Adzacam commited on
Commit
64bc767
·
1 Parent(s): 120ac93

feat: implement marketing data ingestion with normalization and add bulk deduplication/cleaning to financial records

Browse files
Files changed (2) hide show
  1. app.py +112 -2
  2. diccionario_normalizacion.json +224 -0
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import os
 
2
  import datetime
3
  import logging
4
  import re
@@ -8,6 +9,14 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator
8
  import rapidfuzz
9
  import pandas as pd
10
  from typing import List, Optional, Dict, Any
 
 
 
 
 
 
 
 
11
  from sqlalchemy.orm import Session
12
  from database import (
13
  get_db,
@@ -784,8 +793,71 @@ def procesar_lote_encuestas(payloads: List[Dict[str, Any]], db: Session = Depend
784
 
785
  @app.post("/api/v1/ingest/marketing", status_code=status.HTTP_201_CREATED)
786
  def procesar_lote_marketing(payloads: List[Dict[str, Any]], db: Session = Depends(get_db)):
787
- """Ruta para OKRs de Marketing (Placeholder)"""
788
- return {"status": "success", "message": "Ruta de marketing lista para implementación"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
789
 
790
  @app.post("/api/v1/ingesta/financiera/bulk", status_code=status.HTTP_201_CREATED)
791
  def procesar_lote_financiero(payloads: List[FinancePayload], db: Session = Depends(get_db), current_user: Users = Depends(get_current_user)):
@@ -1272,6 +1344,44 @@ def batch_analyze_nlp(
1272
  else:
1273
  records = payload.records
1274
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1275
  results = []
1276
  estudiantes_existentes = db.query(DimEstudiante).all()
1277
 
 
1
  import os
2
+ import json
3
  import datetime
4
  import logging
5
  import re
 
9
  import rapidfuzz
10
  import pandas as pd
11
  from typing import List, Optional, Dict, Any
12
+
13
+ # ── Carga del diccionario de normalización ────────────────────────────────────
14
+ _DICT_PATH = os.path.join(os.path.dirname(__file__), "diccionario_normalizacion.json")
15
+ try:
16
+ with open(_DICT_PATH, "r", encoding="utf-8") as _f:
17
+ DICCIONARIO_NORMALIZACION = json.load(_f)
18
+ except Exception:
19
+ DICCIONARIO_NORMALIZACION = {}
20
  from sqlalchemy.orm import Session
21
  from database import (
22
  get_db,
 
793
 
794
  @app.post("/api/v1/ingest/marketing", status_code=status.HTTP_201_CREATED)
795
  def procesar_lote_marketing(payloads: List[Dict[str, Any]], db: Session = Depends(get_db)):
796
+ """
797
+ Bulk Insert real de datos de Marketing/Ventas en fact_marketing.
798
+ Recibe una lista de dicts con raw_data del frontend y los inserta
799
+ resolviendo las dimensiones id_modulo e id_tiempo.
800
+ """
801
+ try:
802
+ from sqlalchemy import func
803
+
804
+ existing_modulos = {m.nombre_modulo: m.id_modulo for m in db.query(DimModulo).all()}
805
+ existing_tiempos = {t.id_tiempo for t in db.query(DimTiempo).all()}
806
+
807
+ hechos_mkt = []
808
+ for p in payloads:
809
+ raw = p.get("raw_data", p) if isinstance(p, dict) else p
810
+
811
+ # Resolver módulo/programa
812
+ programa_raw = raw.get("programa") or raw.get("modulo") or raw.get("data_domain", "Marketing General")
813
+ # Aplicar normalización del diccionario
814
+ norm_progs = DICCIONARIO_NORMALIZACION.get("programas_cursos", {})
815
+ programa_clean = norm_progs.get(programa_raw, programa_raw)
816
+
817
+ id_modulo_val = existing_modulos.get(programa_clean)
818
+ if not id_modulo_val:
819
+ nuevo_modulo = DimModulo(
820
+ nombre_modulo=programa_clean,
821
+ nombre_institucion=raw.get("institucion", "GiraGroup"),
822
+ programa=programa_clean
823
+ )
824
+ db.add(nuevo_modulo)
825
+ db.flush()
826
+ id_modulo_val = nuevo_modulo.id_modulo
827
+ existing_modulos[programa_clean] = id_modulo_val
828
+
829
+ # Resolver tiempo
830
+ id_tiempo_val = raw.get("id_tiempo", 1)
831
+ if id_tiempo_val not in existing_tiempos:
832
+ db.add(DimTiempo(id_tiempo=id_tiempo_val, gestion=2026, semestre=1, mes="Junio"))
833
+ db.flush()
834
+ existing_tiempos.add(id_tiempo_val)
835
+
836
+ # Extraer métricas de marketing
837
+ leads_val = int(raw.get("leads", 1))
838
+ reservas_val = int(raw.get("reservas", 0))
839
+ inscritos_val = int(raw.get("inscritos", 0))
840
+ costo_val = float(raw.get("costo", raw.get("costo_programa", 0)))
841
+
842
+ hechos_mkt.append(FactMarketingInscripciones(
843
+ id_modulo=id_modulo_val,
844
+ id_tiempo=id_tiempo_val,
845
+ leads=leads_val,
846
+ reservas=reservas_val,
847
+ inscritos=inscritos_val,
848
+ costo_programa=costo_val
849
+ ))
850
+
851
+ if hechos_mkt:
852
+ db.add_all(hechos_mkt)
853
+ db.commit()
854
+
855
+ return {"status": "success", "inserted": len(hechos_mkt)}
856
+ except Exception as e:
857
+ db.rollback()
858
+ import traceback
859
+ traceback.print_exc()
860
+ raise HTTPException(status_code=500, detail=str(e))
861
 
862
  @app.post("/api/v1/ingesta/financiera/bulk", status_code=status.HTTP_201_CREATED)
863
  def procesar_lote_financiero(payloads: List[FinancePayload], db: Session = Depends(get_db), current_user: Users = Depends(get_current_user)):
 
1344
  else:
1345
  records = payload.records
1346
 
1347
+ # ── Deduplicación con Pandas (todas las columnas) ─────────────────────
1348
+ try:
1349
+ records_dicts = [r.model_dump() if hasattr(r, 'model_dump') else r.dict() for r in records]
1350
+ df_records = pd.DataFrame(records_dicts)
1351
+ original_count = len(df_records)
1352
+ df_records = df_records.drop_duplicates()
1353
+ dedup_count = original_count - len(df_records)
1354
+ if dedup_count > 0:
1355
+ logger.info(f"drop_duplicates eliminó {dedup_count} registros duplicados exactos de {original_count}")
1356
+ # ── Normalización con diccionario ─────────────────────────────────
1357
+ norm_progs = DICCIONARIO_NORMALIZACION.get("programas_cursos", {})
1358
+ norm_ciudades = DICCIONARIO_NORMALIZACION.get("departamentos_ciudades", {})
1359
+ norm_grupos = DICCIONARIO_NORMALIZACION.get("grupos_sede", {})
1360
+ norm_estados = DICCIONARIO_NORMALIZACION.get("estados_financieros", {})
1361
+ norm_estados_arca = DICCIONARIO_NORMALIZACION.get("estados_academicos_arca", {})
1362
+ if "programa" in df_records.columns:
1363
+ # Eliminar versiones como "v.3", "2° Versión", "3ª Versión" antes del cruce
1364
+ df_records["programa"] = df_records["programa"].str.replace(r'(?i)\s*(v\.\d+|\d+[°ª]\s*Versi[oó]n).*$', '', regex=True)
1365
+ df_records["programa"] = df_records["programa"].replace(norm_progs)
1366
+ if "modulo" in df_records.columns:
1367
+ df_records["modulo"] = df_records["modulo"].replace(norm_progs)
1368
+ if "ciudad" in df_records.columns:
1369
+ df_records["ciudad"] = df_records["ciudad"].replace(norm_ciudades)
1370
+ df_records["ciudad"] = df_records["ciudad"].replace(norm_grupos)
1371
+ if "estado_cartera" in df_records.columns:
1372
+ df_records["estado_cartera"] = df_records["estado_cartera"].replace(norm_estados)
1373
+ for col_arca in ["estado_academico", "estado_tutoria", "estado_arca"]:
1374
+ if col_arca in df_records.columns:
1375
+ df_records[col_arca] = df_records[col_arca].replace(norm_estados_arca)
1376
+ # Reconstruir records desde DataFrame normalizado
1377
+ if hasattr(records[0], 'model_validate'):
1378
+ RecordClass = type(records[0])
1379
+ records = [RecordClass.model_validate(row) for row in df_records.to_dict(orient="records")]
1380
+ else:
1381
+ records = [type(records[0])(**row) for row in df_records.to_dict(orient="records")]
1382
+ except Exception as e:
1383
+ logger.warning(f"drop_duplicates/normalización falló (procesando sin dedup): {e}")
1384
+
1385
  results = []
1386
  estudiantes_existentes = db.query(DimEstudiante).all()
1387
 
diccionario_normalizacion.json ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "programas_cursos": {
3
+ "MBA": "Administración de Empresas (MBA)",
4
+ "Maestria MBA": "Administración de Empresas (MBA)",
5
+ "Maestría MBA": "Administración de Empresas (MBA)",
6
+ "Maestria Administracion": "Administración de Empresas (MBA)",
7
+ "Maestría Administración": "Administración de Empresas (MBA)",
8
+ "Maestria Transformacion Digital": "Transformación Digital e Innovación Empresarial Basada en Inteligencia Artificial",
9
+ "Maestría Transformación Digital": "Transformación Digital e Innovación Empresarial Basada en Inteligencia Artificial",
10
+ "Maestria IA": "Transformación Digital e Innovación Empresarial Basada en Inteligencia Artificial",
11
+ "Maestría IA": "Transformación Digital e Innovación Empresarial Basada en Inteligencia Artificial",
12
+ "Maestria Neurociencia": "Neurociencia Aplicada",
13
+ "Maestría Neurociencia": "Neurociencia Aplicada",
14
+ "Maestria Marketing": "Marketing Inteligente y Gestión Comercial",
15
+ "Maestría Marketing": "Marketing Inteligente y Gestión Comercial",
16
+ "Maestria Marketing Inteligente": "Marketing Inteligente y Gestión Comercial",
17
+ "Maestría Marketing Inteligente": "Marketing Inteligente y Gestión Comercial",
18
+ "Maestria Gestion Hospitalaria": "Gestión Hospitalaria y Gerencia de Salud",
19
+ "Maestría Gestión Hospitalaria": "Gestión Hospitalaria y Gerencia de Salud",
20
+ "Maestria Salud": "Gestión Hospitalaria y Gerencia de Salud",
21
+ "Maestría Salud": "Gestión Hospitalaria y Gerencia de Salud",
22
+ "Diplomado Educacion Superior": "Educación Superior por Competencias e Innovación Tecnológica",
23
+ "Diplomado Educación Superior": "Educación Superior por Competencias e Innovación Tecnológica",
24
+ "Diplomado TIC": "Educación Superior por Competencias e Innovación Tecnológica",
25
+ "Diplomado TICs": "Educación Superior por Competencias e Innovación Tecnológica",
26
+ "Diplomado IA Educacion": "Inteligencia Artificial Aplicada a la Educación",
27
+ "Diplomado IA Educación": "Inteligencia Artificial Aplicada a la Educación",
28
+ "Diplomado Marketing Digital": "Marketing Digital y Social Ads",
29
+ "Diplomado Mkt Digital": "Marketing Digital y Social Ads",
30
+ "Diplomado Social Ads": "Marketing Digital y Social Ads",
31
+ "Diplomado Marketing Farmaceutico": "Marketing Farmacéutico",
32
+ "Diplomado Marketing Farmacéutico": "Marketing Farmacéutico",
33
+ "Diplomado Mkt Farma": "Marketing Farmacéutico",
34
+ "Diplomado Gerencia Financiera": "Gerencia Financiera y Administración de Negocios",
35
+ "Diplomado Finanzas": "Gerencia Financiera y Administración de Negocios",
36
+ "Diplomado Medicina Estetica": "Medicina Estética",
37
+ "Diplomado Medicina Estética": "Medicina Estética",
38
+ "Diplomado Ozonoterapia": "Protocolos Fundamentales en Ozonoterapia",
39
+ "Diplomado Preparaciones Magistrales": "Preparaciones Magistrales y Buenas Prácticas de Elaboración",
40
+ "Diplomado Emergencias Medicas": "Emergencias Médicas: Traumas y Soporte Vital Avanzado",
41
+ "Diplomado Emergencias Médicas": "Emergencias Médicas: Traumas y Soporte Vital Avanzado",
42
+ "Diplomado Derecho Bancario": "Derecho Bancario y Bursátil (Enfoque Nacional e Internacional)",
43
+ "Diplomado Talento Humano": "Gestión y Administración de Talento Humano",
44
+ "Diplomado RRHH": "Gestión y Administración de Talento Humano",
45
+ "Diplomado RR.HH.": "Gestión y Administración de Talento Humano",
46
+ "Maestr. RRHH": "Gestión y Administración de Talento Humano",
47
+ "Maestría en RR.HH.": "Gestión y Administración de Talento Humano",
48
+ "Diplomado Psicologia Clinica": "Psicología Clínica y Psicoterapia Centrada en Procesos",
49
+ "Diplomado Psicología Clínica": "Psicología Clínica y Psicoterapia Centrada en Procesos",
50
+ "Experto Costos": "Gestión Estratégica de Costos",
51
+ "Experto Gestion Costos": "Gestión Estratégica de Costos",
52
+ "Experto Gestión Costos": "Gestión Estratégica de Costos",
53
+ "Experto Asesoria Financiera": "Asesoría y Gestión Financiera",
54
+ "Experto Asesoría Financiera": "Asesoría y Gestión Financiera",
55
+ "Experto Riesgo Financiero": "Gestión Integral de Riesgo Financiero",
56
+ "Experto IA TICs Educacion": "Inteligencia Artificial y TICs Aplicadas a la Educación",
57
+ "Experto IA TICs Educación": "Inteligencia Artificial y TICs Aplicadas a la Educación",
58
+ "Curso Edicion Video": "Edición de Video para Redes Sociales",
59
+ "Curso Edición Video": "Edición de Video para Redes Sociales",
60
+ "Curso Direccion Comercial": "Dirección Estratégica Comercial",
61
+ "Curso Dirección Comercial": "Dirección Estratégica Comercial",
62
+ "Curso Marketing Digital": "Marketing Digital (Curso corto)",
63
+ "Curso Salud Mental Infanto": "Salud Mental Infanto – Juvenil: Psicopatología de los Trastornos Mentales más Frecuentes",
64
+ "Curso Fotografia Odontologia": "Fotografía en Odontología y su Importancia Clínica",
65
+ "Curso Fotografía Odontología": "Fotografía en Odontología y su Importancia Clínica",
66
+ "Curso Flebologia": "Flebología y Linfología (Escleroterapia)",
67
+ "Curso Flebología": "Flebología y Linfología (Escleroterapia)",
68
+ "Curso Escritura Legal": "Curso en Escritura Legal y Jurisprudencia",
69
+ "Curso Obstetricia": "Curso en Obstetricia Clínica Integral",
70
+ "Curso Residencia Medica": "Curso Preparatorio para la Residencia Médica",
71
+ "Curso Residencia Médica": "Curso Preparatorio para la Residencia Médica",
72
+ "Curso Tablas Dinamicas": "Curso Tablas Dinámicas en Microsoft Excel",
73
+ "Curso Tablas Dinámicas": "Curso Tablas Dinámicas en Microsoft Excel",
74
+ "Curso Excel": "Curso Tablas Dinámicas en Microsoft Excel",
75
+ "Curso Contratacion Estado": "Curso Especializado en Procesos de Contratación del Estado",
76
+ "Curso Contratación Estado": "Curso Especializado en Procesos de Contratación del Estado",
77
+ "Curso Ethical Hacking": "Curso en Ethical Hacking-Pentesting",
78
+ "Curso Pentesting": "Curso en Ethical Hacking-Pentesting",
79
+ "Curso Psicologia Consumidor": "Curso en Psicología del Consumidor",
80
+ "Curso Psicología Consumidor": "Curso en Psicología del Consumidor",
81
+ "Curso Ciberseguridad": "Curso en Ciberseguridad y Educación Financiera",
82
+ "Curso Oficiales Credito": "Curso Formación de Oficiales de Crédito",
83
+ "Curso Oficiales Crédito": "Curso Formación de Oficiales de Crédito",
84
+ "Curso Herramientas Tecnologicas": "Curso en Uso de Herramientas Tecnológicas para Educadores",
85
+ "Curso Herramientas Tecnológicas": "Curso en Uso de Herramientas Tecnológicas para Educadores",
86
+ "Curso Bienestar Salud Mental": "Curso en Bienestar y Salud Mental en el Trabajo",
87
+ "Curso Redaccion Creativa": "Curso de Redacción Creativa para Redes Sociales",
88
+ "Curso Redacción Creativa": "Curso de Redacción Creativa para Redes Sociales",
89
+ "Curso Inteligencia Empresarial": "Curso Inteligencia Empresarial con Microsoft Excel",
90
+ "Curso Abdomen Agudo": "Curso en Manejo del Abdomen Agudo Quirúrgico",
91
+ "Curso Contenidos Educacion": "Curso en Creación de Contenidos en Educación en Redes Sociales",
92
+ "Curso Contenidos Educación": "Curso en Creación de Contenidos en Educación en Redes Sociales",
93
+ "Curso Atencion Cliente": "Curso en Atención al Cliente y Manejo de Conflictos",
94
+ "Curso Atención Cliente": "Curso en Atención al Cliente y Manejo de Conflictos"
95
+ },
96
+ "departamentos_ciudades": {
97
+ "Lp": "La Paz",
98
+ "LP": "La Paz",
99
+ "LPZ": "La Paz",
100
+ "Lpz": "La Paz",
101
+ "la paz": "La Paz",
102
+ "LA PAZ": "La Paz",
103
+ "Cbba": "Cochabamba",
104
+ "CBBA": "Cochabamba",
105
+ "cbba": "Cochabamba",
106
+ "cochabamba": "Cochabamba",
107
+ "COCHABAMBA": "Cochabamba",
108
+ "Scz": "Santa Cruz",
109
+ "SCZ": "Santa Cruz",
110
+ "scz": "Santa Cruz",
111
+ "Santa cruz": "Santa Cruz",
112
+ "santa cruz": "Santa Cruz",
113
+ "SANTA CRUZ": "Santa Cruz",
114
+ "Oruro": "Oruro",
115
+ "ORURO": "Oruro",
116
+ "Potosi": "Potosí",
117
+ "POTOSI": "Potosí",
118
+ "potosi": "Potosí",
119
+ "Sucre": "Sucre",
120
+ "SUCRE": "Sucre",
121
+ "Chuquisaca": "Sucre",
122
+ "CHUQUISACA": "Sucre",
123
+ "Tarija": "Tarija",
124
+ "TARIJA": "Tarija",
125
+ "Beni": "Trinidad",
126
+ "BENI": "Trinidad",
127
+ "Trinidad": "Trinidad",
128
+ "TRINIDAD": "Trinidad",
129
+ "Pando": "Cobija",
130
+ "PANDO": "Cobija",
131
+ "Cobija": "Cobija",
132
+ "COBIJA": "Cobija",
133
+ "El Alto": "El Alto",
134
+ "EL ALTO": "El Alto",
135
+ "el alto": "El Alto"
136
+ },
137
+ "universidades_instituciones": {
138
+ "GiraGroup": "GiraGroup Centro de Formación Continua",
139
+ "Gira Group": "GiraGroup Centro de Formación Continua",
140
+ "GIRAGROUP": "GiraGroup Centro de Formación Continua",
141
+ "giragroup": "GiraGroup Centro de Formación Continua",
142
+ "Gira": "GiraGroup Centro de Formación Continua",
143
+ "UMSA": "Universidad Mayor de San Andrés",
144
+ "UMSS": "Universidad Mayor de San Simón",
145
+ "UAGRM": "Universidad Autónoma Gabriel René Moreno",
146
+ "UCB": "Universidad Católica Boliviana San Pablo",
147
+ "UPB": "Universidad Privada Boliviana",
148
+ "UPEA": "Universidad Pública de El Alto",
149
+ "UTB": "Universidad Técnica de Bolivia",
150
+ "EMI": "Escuela Militar de Ingeniería",
151
+ "UNIVALLE": "Universidad Privada del Valle",
152
+ "UTO": "Universidad Técnica de Oruro",
153
+ "USFX": "Universidad Mayor, Real y Pontificia de San Francisco Xavier de Chuquisaca",
154
+ "UNIFRANZ": "Universidad Franz Tamayo",
155
+ "unifranz": "Universidad Franz Tamayo",
156
+ "Unifranz": "Universidad Franz Tamayo"
157
+ },
158
+ "estados_financieros": {
159
+ "AL DIA": "AL DÍA",
160
+ "al dia": "AL DÍA",
161
+ "Al Dia": "AL DÍA",
162
+ "Al dia": "AL DÍA",
163
+ "al día": "AL DÍA",
164
+ "ALDIA": "AL DÍA",
165
+ "EN MORA": "MORA",
166
+ "en mora": "MORA",
167
+ "En Mora": "MORA",
168
+ "mora": "MORA",
169
+ "PENDIENTE": "PENDIENTE",
170
+ "pendiente": "PENDIENTE",
171
+ "PAGADO": "PAGADO",
172
+ "pagado": "PAGADO",
173
+ "Pagado": "PAGADO",
174
+ "PROYECTADO": "PROYECTADO",
175
+ "proyectado": "PROYECTADO",
176
+ "PENDIENTE DE PAGO": "PENDIENTE",
177
+ "DERIVADO A LEGAL": "LEGAL",
178
+ "ACUERDO DE PAGO FIRMADO": "ACUERDO DE PAGO",
179
+ "EN GESTIÓN DE COBRO": "GESTIÓN DE COBRO",
180
+ "CARTERA CERRADA": "CARTERA CERRADA",
181
+ "CARTERA EN MORA": "CARTERA EN MORA",
182
+ "CARTERA VIGENTE": "CARTERA VIGENTE",
183
+ "CARTERA DADO DE BAJA": "CARTERA DADO DE BAJA"
184
+ },
185
+ "grupos_sede": {
186
+ "Grupo A - La Paz": "La Paz",
187
+ "Grupo B - La Paz": "La Paz",
188
+ "Grupo C - La Paz": "La Paz",
189
+ "Grupo A - Cochabamba": "Cochabamba",
190
+ "Grupo Cbba": "Cochabamba",
191
+ "Grupo CBBA": "Cochabamba",
192
+ "Grupo El Alto": "El Alto",
193
+ "Grupo C - El Alto": "El Alto",
194
+ "Grupo A - El Alto": "El Alto",
195
+ "Grupo B - El Alto": "El Alto",
196
+ "Grupo A - Santa Cruz": "Santa Cruz",
197
+ "Grupo B - Santa Cruz": "Santa Cruz",
198
+ "Grupo SCZ": "Santa Cruz",
199
+ "Grupo A - Oruro": "Oruro",
200
+ "Grupo B - Oruro": "Oruro",
201
+ "Grupo A - Sucre": "Sucre",
202
+ "Grupo B - Sucre": "Sucre",
203
+ "Grupo A - Tarija": "Tarija",
204
+ "Grupo B - Tarija": "Tarija",
205
+ "Grupo A - Trinidad": "Trinidad",
206
+ "Grupo A - Cobija": "Cobija",
207
+ "Grupo Virtual": "Virtual",
208
+ "Grupo Online": "Virtual",
209
+ "VIRTUAL": "Virtual",
210
+ "Online": "Virtual"
211
+ },
212
+ "estados_academicos_arca": {
213
+ "Aprobado": "APROBADO",
214
+ "Reprobado": "REPROBADO",
215
+ "Abandono": "ABANDONO",
216
+ "En Proceso": "EN PROCESO",
217
+ "Atrasado": "ATRASADO",
218
+ "Al Dia": "AL DÍA",
219
+ "Sin Tutoria": "SIN TUTORÍA",
220
+ "Emitido": "EMITIDO",
221
+ "Pendiente": "PENDIENTE",
222
+ "En Tramite": "EN TRÁMITE"
223
+ }
224
+ }