55Usado cuando el wizard de postulación termina (estado COMPLETED).
66"""
77
8+ import json
89import logging
910import os
1011from typing import Any
2021BACKOFFICE_ADMIN_PASSWORD = os .getenv ("BACKOFFICE_ADMIN_PASSWORD" , "admin123" )
2122
2223
24+ def _read_default_id_estado () -> int :
25+ raw = (os .getenv ("BACKOFFICE_DEFAULT_ID_ESTADO" ) or "1" ).strip ()
26+ try :
27+ return int (raw )
28+ except ValueError :
29+ logger .warning (
30+ "[BACKOFFICE] BACKOFFICE_DEFAULT_ID_ESTADO invalido=%r, se usa 1 por defecto." ,
31+ raw ,
32+ )
33+ return 1
34+
35+
36+ BACKOFFICE_DEFAULT_ID_ESTADO = _read_default_id_estado ()
37+
38+
2339def _parse_full_name (full_name : str ) -> tuple [str , str ]:
2440 """Convierte 'Apellido, Nombre' o 'Nombre Apellido' en (nombre, apellido)."""
2541 if not (full_name or full_name .strip ()):
@@ -38,7 +54,7 @@ def _parse_full_name(full_name: str) -> tuple[str, str]:
3854
3955def _parse_location (location : str ) -> tuple [str , str ]:
4056 """Intenta extraer país y ciudad de un string 'País, Ciudad' o similar."""
41- if not ( location or location .strip () ):
57+ if not location or not location .strip ():
4258 return "" , ""
4359 s = location .strip ()
4460 if "," in s :
@@ -47,6 +63,59 @@ def _parse_location(location: str) -> tuple[str, str]:
4763 return s , ""
4864
4965
66+ def _resolve_id_convocatoria (id_convocatoria : int | None ) -> int | None :
67+ """Resolve id_convocatoria from argument or env, if present and valid."""
68+ if id_convocatoria is not None :
69+ return id_convocatoria
70+
71+ raw = (os .getenv ("BACKOFFICE_ID_CONVOCATORIA" ) or "" ).strip ()
72+ if not raw :
73+ return None
74+
75+ try :
76+ return int (raw )
77+ except ValueError :
78+ logger .warning (
79+ "[BACKOFFICE] BACKOFFICE_ID_CONVOCATORIA invalido=%r, se ignora." ,
80+ raw ,
81+ )
82+ return None
83+
84+
85+ def _build_caso_description (wizard_responses : dict [str , Any ]) -> str :
86+ """Build a non-empty case description from wizard fields."""
87+ desc_parts = []
88+ if wizard_responses .get ("problem_description" ):
89+ desc_parts .append (f"Problema: { str (wizard_responses .get ('problem_description' ))[:1000 ]} " )
90+ if wizard_responses .get ("solution_description" ):
91+ desc_parts .append (f"Solucion: { str (wizard_responses .get ('solution_description' ))[:1000 ]} " )
92+ if wizard_responses .get ("motivation" ):
93+ desc_parts .append (f"Motivacion: { str (wizard_responses .get ('motivation' ))[:500 ]} " )
94+ if wizard_responses .get ("additional_comments" ):
95+ desc_parts .append (f"Comentarios: { str (wizard_responses .get ('additional_comments' ))[:1000 ]} " )
96+
97+ if desc_parts :
98+ return "\n \n " .join (desc_parts )
99+ return "Postulacion generada desde ChatBot."
100+
101+
102+ def _sanitize_chatbot_data (wizard_responses : dict [str , Any ]) -> dict [str , Any ]:
103+ """Keep JSON-serializable values for datos_chatbot."""
104+ sanitized : dict [str , Any ] = {}
105+ for key , value in (wizard_responses or {}).items ():
106+ if value is None or value == "" :
107+ continue
108+ if isinstance (value , (str , int , float , bool , list , dict )):
109+ sanitized [key ] = value
110+ continue
111+ try :
112+ json .dumps (value )
113+ sanitized [key ] = value
114+ except (TypeError , ValueError ):
115+ sanitized [key ] = str (value )
116+ return sanitized
117+
118+
50119def build_emprendedor_payload (wizard_responses : dict [str , Any ]) -> dict [str , Any ]:
51120 """Mapea wizard_responses al body de POST /api/v1/emprendedores/."""
52121 full_name = (wizard_responses .get ("full_name" ) or "" ).strip ()
@@ -97,8 +166,8 @@ def build_caso_payload(
97166 id_convocatoria : int | None = None ,
98167) -> dict [str , Any ]:
99168 """Mapea wizard_responses al body de POST /api/v1/casos/."""
100- # nombre_caso: usar problema/idea si existe, sino nombre genérico
101- nombre_caso = "Postulación desde ChatBot"
169+ # nombre_caso: usar problema/idea si existe, sino nombre generico
170+ nombre_caso = "Postulacion desde ChatBot"
102171 if wizard_responses .get ("problem_description" ):
103172 raw = str (wizard_responses .get ("problem_description" ))[:200 ]
104173 nombre_caso = raw if raw else nombre_caso
@@ -109,26 +178,25 @@ def build_caso_payload(
109178 payload : dict [str , Any ] = {
110179 "nombre_caso" : nombre_caso [:200 ],
111180 "id_emprendedor" : id_emprendedor ,
181+ "descripcion" : _build_caso_description (wizard_responses ),
182+ "consentimiento_datos" : True ,
183+ "id_estado" : BACKOFFICE_DEFAULT_ID_ESTADO ,
112184 }
113- # descripcion: resumen opcional
114- desc_parts = []
115- if wizard_responses .get ("solution_description" ):
116- desc_parts .append (str (wizard_responses .get ("solution_description" ))[:1000 ])
117- if wizard_responses .get ("motivation" ):
118- desc_parts .append (f"Motivación: { str (wizard_responses .get ('motivation' ))[:500 ]} " )
119- if desc_parts :
120- payload ["descripcion" ] = "\n \n " .join (desc_parts )
121- # datos_chatbot: respuestas estructuradas del wizard (recomendado por la integración)
122- datos : dict [str , Any ] = {k : v for k , v in wizard_responses .items () if v is not None and v != "" }
185+
186+ # datos_chatbot: respuestas estructuradas del wizard (recomendado por la integracion)
187+ datos = _sanitize_chatbot_data (wizard_responses )
123188 if datos :
124189 payload ["datos_chatbot" ] = datos
125- if id_convocatoria is not None :
126- payload ["id_convocatoria" ] = id_convocatoria
190+
191+ resolved_convocatoria = _resolve_id_convocatoria (id_convocatoria )
192+ if resolved_convocatoria is not None :
193+ payload ["id_convocatoria" ] = resolved_convocatoria
127194
128195 logger .debug (
129- "[BACKOFFICE] build_caso_payload: id_emprendedor=%s, id_convocatoria=%s, keys=%s" ,
196+ "[BACKOFFICE] build_caso_payload: id_emprendedor=%s, id_convocatoria=%s, id_estado=%s, keys=%s" ,
130197 id_emprendedor ,
131- id_convocatoria ,
198+ resolved_convocatoria ,
199+ payload .get ("id_estado" ),
132200 list (payload .keys ()),
133201 )
134202
@@ -172,12 +240,14 @@ async def send_postulation_to_backoffice(
172240 """
173241 Ejecuta el flujo: login -> crear emprendedor -> crear caso.
174242 Devuelve (id_emprendedor, id_caso).
175- Lanza excepción si la API no está configurada o falla.
243+ Lanza excepcion si la API no esta configurada o falla.
176244 """
245+ resolved_convocatoria = _resolve_id_convocatoria (id_convocatoria )
246+
177247 logger .info (
178248 "[BACKOFFICE] send_postulation_to_backoffice llamado: base_url=%s, id_convocatoria=%s, total_campos_respuestas=%s" ,
179249 BACKOFFICE_BASE_URL ,
180- id_convocatoria ,
250+ resolved_convocatoria ,
181251 len (wizard_responses or {}),
182252 )
183253 disabled = os .getenv ("BACKOFFICE_INTEGRATION_ENABLED" , "true" ).lower () in ("0" , "false" , "no" )
@@ -192,7 +262,7 @@ async def send_postulation_to_backoffice(
192262 async with aiohttp .ClientSession () as session :
193263 token = await _get_access_token (session )
194264 headers ["Authorization" ] = f"Bearer { token } "
195- logger .debug ("[BACKOFFICE] Autenticado contra Backoffice, iniciando creación de emprendedor." )
265+ logger .debug ("[BACKOFFICE] Autenticado contra Backoffice, iniciando creacion de emprendedor." )
196266
197267 # 1) Crear emprendedor
198268 emprendedor_body = build_emprendedor_payload (wizard_responses )
@@ -223,7 +293,7 @@ async def send_postulation_to_backoffice(
223293 logger .info ("[BACKOFFICE] Emprendedor creado id_emprendedor=%s" , id_emprendedor )
224294
225295 # 2) Crear caso
226- caso_body = build_caso_payload (wizard_responses , id_emprendedor , id_convocatoria )
296+ caso_body = build_caso_payload (wizard_responses , id_emprendedor , resolved_convocatoria )
227297 url_caso = f"{ BACKOFFICE_BASE_URL } { BACKOFFICE_API_PREFIX } /casos/"
228298 logger .debug (
229299 "[BACKOFFICE] POST /casos: url=%s, payload_keys=%s" ,
0 commit comments