@innvisor/conny-ai 9.7.0 → 9.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +17 -1
  3. package/conny_app.py +8 -2
  4. package/conny_cli.py +103 -11
  5. package/conny_core/evolution.py +112 -0
  6. package/conny_core/first_turn_ops.py +16 -20
  7. package/conny_core/prompt_ops.py +62 -0
  8. package/conny_demo_voice.py +1 -1
  9. package/conny_doctor.py +287 -2
  10. package/conny_domino.py +2 -2
  11. package/conny_generator.py +1 -1
  12. package/conny_init.py +234 -41
  13. package/conny_runtime_ops.py +198 -6
  14. package/conny_ultra_config.py +25 -11
  15. package/conny_utils.py +21 -3
  16. package/ecosystem.config.js +11 -1
  17. package/install.sh +78 -22
  18. package/npm/conny.js +73 -17
  19. package/package.json +13 -3
  20. package/run.sh +7 -0
  21. package/src/conny/admin/dashboard.py +35 -4
  22. package/src/conny/admin_memory.py +93 -0
  23. package/src/conny/api/routes.py +26 -9
  24. package/src/conny/channels/cli.py +30 -9
  25. package/src/conny/demo/handler.py +23 -23
  26. package/src/conny/personas/generator.py +1 -1
  27. package/src/conny/production/domino.py +2 -2
  28. package/src/conny/production/guard.py +4 -4
  29. package/src/core/admin_engines.py +51 -48
  30. package/src/core/globals.py +110 -9
  31. package/src/core/production_monitor.py +63 -38
  32. package/src/core/runtime.py +343 -305
  33. package/src/domain/prompts/prospect_pitch.py +11 -11
  34. package/src/domain/send_guard.py +4 -4
  35. package/src/interfaces/web/app.py +91 -27
  36. package/src/interfaces/web/demo_admin_commands.py +165 -0
  37. package/src/interfaces/web/demo_handler.py +178 -34
  38. package/brand-assets/cl-nica-de-las-am-ricas/manifest.json +0 -22
  39. package/brand-assets/cl-nica-de-las-am-ricas/processed/business-identity.txt +0 -11
  40. package/brand-assets/cl-nica-de-las-am-ricas/raw/business-identity.txt +0 -11
  41. package/brand-assets/cl-nica-las-am-ricas/manifest.json +0 -22
  42. package/brand-assets/cl-nica-las-am-ricas/processed/business-identity.txt +0 -11
  43. package/brand-assets/cl-nica-las-am-ricas/raw/business-identity.txt +0 -11
  44. package/brand-assets/conny-demo/manifest.json +0 -22
  45. package/brand-assets/conny-demo/processed/business-identity.txt +0 -7
  46. package/brand-assets/conny-demo/raw/business-identity.txt +0 -7
  47. package/fix_init.py +0 -27
  48. package/verify_conversation_impl.py +0 -48
@@ -17,7 +17,9 @@ async def handle_demo_message(
17
17
  ) -> List[str]:
18
18
  # Local imports from conny.py to avoid circular dependencies
19
19
  import sys
20
- conny_module = sys.modules[self.__class__.__module__]
20
+ runtime_module = sys.modules[self.__class__.__module__]
21
+ facade_name = getattr(runtime_module, "__facade_name__", "")
22
+ conny_module = sys.modules.get(facade_name) or runtime_module
21
23
  Config = conny_module.Config
22
24
  db = conny_module.db
23
25
  now_col = conny_module.now_col
@@ -58,9 +60,16 @@ async def handle_demo_message(
58
60
  owner_confusion_or_language_signal as _owner_confusion_or_language_signal
59
61
  )
60
62
 
63
+ from src.interfaces.web.demo_admin_commands import (
64
+ _strip_jid as _strip_jid,
65
+ is_admin_chat as _is_admin_demo,
66
+ looks_like_contact_command as _looks_like_contact_cmd,
67
+ handle_admin_contact_command as _handle_admin_contact_cmd,
68
+ )
69
+
61
70
  # Check if optional symbols are available
62
71
  _SESSION_MANAGER_AVAILABLE = getattr(conny_module, "_SESSION_MANAGER_AVAILABLE", False)
63
- _BLACKONE_PATCHES = getattr(conny_module, "_BLACKONE_PATCHES", False)
72
+ _INNVISOR_PATCHES = getattr(conny_module, "_INNVISOR_PATCHES", False)
64
73
  _CONNY_DOMINO_AVAILABLE = getattr(conny_module, "_CONNY_DOMINO_AVAILABLE", False)
65
74
  build_demo_domino_payload = getattr(conny_module, "build_demo_domino_payload", None)
66
75
  build_prospect_pitch_system_prompt = getattr(conny_module, "build_prospect_pitch_system_prompt", None)
@@ -139,7 +148,9 @@ async def handle_demo_message(
139
148
  del self._demo_sessions[k]
140
149
  try:
141
150
  with db._conn() as c:
142
- c.execute("DELETE FROM conversations WHERE chat_id=?", (chat_id,))
151
+ existing = c.execute("SELECT COUNT(*) FROM conversations WHERE chat_id=?", (chat_id,)).fetchone()
152
+ if existing and existing[0] <= 2:
153
+ c.execute("DELETE FROM conversations WHERE chat_id=?", (chat_id,))
143
154
  except Exception: pass
144
155
  sk = f"demo_{chat_id}"
145
156
  else:
@@ -182,10 +193,11 @@ async def handle_demo_message(
182
193
  mem = get_memory(instance_id)
183
194
  mem.delete_session_cache(chat_id)
184
195
  except Exception: pass
185
- try:
186
- with db._conn() as c:
187
- c.execute("DELETE FROM conversations WHERE chat_id=?", (chat_id,))
188
- except Exception: pass
196
+ if last_seen > 0:
197
+ try:
198
+ with db._conn() as c:
199
+ c.execute("DELETE FROM conversations WHERE chat_id=?", (chat_id,))
200
+ except Exception: pass
189
201
 
190
202
  history = db.get_history(chat_id) if db else []
191
203
  now_dt = now_col()
@@ -217,6 +229,8 @@ async def handle_demo_message(
217
229
  ready_for_customer = bool(self._demo_sessions.get(bready_key, False))
218
230
  llm_runtime_ready = self._llm_runtime_available()
219
231
 
232
+ is_admin_demo = _is_admin_demo(chat_id, clinic, db) if not sim_mode_active else False
233
+
220
234
  def _detect_demo_owner_language(raw_text: str, current_lang: str = "es") -> str:
221
235
  normalized = _normalize_conv_text(raw_text or "")
222
236
  if not normalized:
@@ -383,14 +397,14 @@ async def handle_demo_message(
383
397
 
384
398
  # ── SEND GUARD — pitch inteligente + fix de cortes ──────────────────
385
399
  _guard = None
386
- if _BLACKONE_PATCHES:
400
+ if _INNVISOR_PATCHES:
387
401
  try:
388
402
  _guard = SendGuard(context="demo", business_name=business_name)
389
403
  except Exception:
390
404
  _guard = None
391
405
 
392
406
  # Smart handoff proactivo ANTES del LLM (prospecto quiere hablar con humano)
393
- if _BLACKONE_PATCHES and _guard:
407
+ if _INNVISOR_PATCHES and _guard:
394
408
  try:
395
409
  _proactive = _guard.check_handoff(text, history)
396
410
  if _proactive and _SMART_HANDOFF and handoff_manager:
@@ -413,8 +427,8 @@ async def handle_demo_message(
413
427
  }
414
428
  )
415
429
  _is_first_demo_turn = not any(m.get("role") == "assistant" for m in history)
416
- # Fix Black One / BlackBoss + cortes ANTES de procesar
417
- if _BLACKONE_PATCHES:
430
+ # Fix Innvisor / BlackBoss + cortes ANTES de procesar
431
+ if _INNVISOR_PATCHES:
418
432
  try:
419
433
  if _guard and business_name:
420
434
  _guard.business_name = business_name
@@ -582,7 +596,7 @@ async def handle_demo_message(
582
596
  _pre_text_low = text.lower().strip()
583
597
 
584
598
  # ── PITCH INTELIGENTE — prospecto B2B confundido ─────────────────────
585
- if _BLACKONE_PATCHES:
599
+ if _INNVISOR_PATCHES:
586
600
  try:
587
601
  _pitch_blockers = (
588
602
  "para que", "para qué", "por que", "por qué",
@@ -796,11 +810,89 @@ async def handle_demo_message(
796
810
  log.error(f"[demo] llm error: {e}")
797
811
  return None
798
812
 
813
+ # ── Admin: beta demo mode toggle — antes de intercept para dejar pasar ──
814
+ _beta_key = sk + "_beta_demo"
815
+ _beta_active = bool(self._demo_sessions.get(_beta_key, False))
816
+ if is_admin_demo:
817
+ _gen_beta = _get_demo_engine() or llm_engine
818
+ if _gen_beta:
819
+ if _beta_active:
820
+ try:
821
+ r, _ = await _gen_beta.complete(
822
+ [{"role": "system", "content": "Responde solo SI o NO. El admin quiere SALIR del modo demo beta y volver a modo admin?"},
823
+ {"role": "user", "content": text}],
824
+ model_tier="fast", temperature=0.1, max_tokens=10,
825
+ )
826
+ if r and r.strip().upper() == "SI":
827
+ self._demo_sessions.pop(_beta_key, None)
828
+ _beta_active = False
829
+ except Exception:
830
+ pass
831
+ else:
832
+ try:
833
+ r, _ = await _gen_beta.complete(
834
+ [{"role": "system", "content": "Responde solo SI o NO. El admin quiere ACTIVAR el modo demo beta para que Conny lo trate como un lead normal?"},
835
+ {"role": "user", "content": text}],
836
+ model_tier="fast", temperature=0.1, max_tokens=10,
837
+ )
838
+ if r and r.strip().upper() == "SI":
839
+ self._demo_sessions[_beta_key] = True
840
+ _beta_active = True
841
+ except Exception:
842
+ pass
843
+
844
+ # ── Admin: intercept ALL admin messages ─────────────────────────────────
845
+ if is_admin_demo and not _beta_active:
846
+ log.info(f"[demo] Admin message from {chat_id}: {text[:80]}")
847
+ _save("user", text)
848
+ _raw_admin = _strip_jid(chat_id)
849
+ _admin_name = ""
850
+ try:
851
+ _admin_rec = db.get_admin(chat_id) or db.get_admin(_raw_admin)
852
+ if _admin_rec:
853
+ _admin_name = _admin_rec.get("name", "") or ""
854
+ except Exception:
855
+ pass
856
+
857
+ if _looks_like_contact_cmd(text):
858
+ result = await _handle_admin_contact_cmd(
859
+ self, chat_id, text, clinic, db,
860
+ llm_engine=llm_engine,
861
+ admin_name=_admin_name,
862
+ demo_llm=_llm,
863
+ )
864
+ if result:
865
+ _save("assistant", " ||| ".join(result))
866
+ return result
867
+ return []
868
+
869
+ # General admin command: LLM conversation
870
+ try:
871
+ sys_ctx = "Eres Conny."
872
+ if _admin_name:
873
+ sys_ctx += f" Le estás respondiendo a {_admin_name}, el admin."
874
+ sys_ctx += " Respondé en 2 burbujas cortas separadas por |||."
875
+ gen_eng = _get_demo_engine() or llm_engine
876
+ if gen_eng:
877
+ r, _ = await gen_eng.complete(
878
+ [{"role": "system", "content": sys_ctx},
879
+ {"role": "user", "content": text}],
880
+ model_tier="fast", temperature=0.82, max_tokens=400,
881
+ )
882
+ if r:
883
+ parts = [p.strip() for p in r.split("|||") if p.strip()]
884
+ if parts:
885
+ _save("assistant", " ||| ".join(parts[:2]))
886
+ return parts[:2]
887
+ except Exception:
888
+ pass
889
+ return []
890
+
799
891
  async def _llm_classify_business_name(raw_text: str) -> Tuple[bool, Optional[str]]:
800
892
  return await llm_classify_business_name(raw_text, _get_demo_engine())
801
893
 
802
894
  async def _llm_conv_pitch(temp=0.85, max_t=8192, recent_limit=12):
803
- """LLM con el pitch de Black One para prospectos confundidos."""
895
+ """LLM con el pitch de Innvisor para prospectos confundidos."""
804
896
  try:
805
897
  pitch_sys = build_prospect_pitch_system_prompt(business_name)
806
898
  except Exception:
@@ -910,8 +1002,8 @@ async def handle_demo_message(
910
1002
  }
911
1003
  )
912
1004
  _is_first_demo_turn = not any(m.get("role") == "assistant" for m in history)
913
- # ── BLACK ONE: fix Black One + cortes antes de procesar ──────────
914
- if _BLACKONE_PATCHES:
1005
+ # ── INNVISOR: fix Innvisor + cortes antes de procesar ──────────
1006
+ if _INNVISOR_PATCHES:
915
1007
  try:
916
1008
  if _guard and business_name:
917
1009
  _guard.business_name = business_name
@@ -1063,7 +1155,7 @@ async def handle_demo_message(
1063
1155
  detail_tokens = ("chat", "cliente", "demo", "tono", "responder", "whatsapp")
1064
1156
  return not any(token in lowered_response for token in detail_tokens)
1065
1157
  if any(token in lowered_user for token in ("quien te hizo", "quién te hizo", "como tenerte", "cómo tenerte", "quien te creo", "quién te creó")):
1066
- return "black one" not in lowered_response or "3124348669" not in lowered_response
1158
+ return "innvisor" not in lowered_response or "3243699856" not in lowered_response
1067
1159
  if any(token in lowered_user for token in ("audio", "audios", "nota de voz", "pdf", "archivo", "documento", "imagen")):
1068
1160
  return not any(token in lowered_response for token in ("audio", "pdf", "documento", "imagen", "transcrib"))
1069
1161
  if any(token in lowered_user for token in ("me mandaron tu numero", "me mandaron tu número", "me pasaron tu numero", "me pasaron tu número", "que haces exactamente", "qué haces exactamente", "no entiendo que haces", "no entiendo qué haces")):
@@ -1249,6 +1341,61 @@ async def handle_demo_message(
1249
1341
  return None, had_output
1250
1342
 
1251
1343
 
1344
+ def _demo_owner_last_resort(
1345
+ user_text: str,
1346
+ *,
1347
+ explain_name: bool = False,
1348
+ current_business_name: str = "",
1349
+ ) -> str:
1350
+ """
1351
+ Last resort only when no usable model response exists.
1352
+ It must keep the demo moving without pretending the network failed.
1353
+ """
1354
+ _biz = (current_business_name or business_name or "").strip()
1355
+ _user = _normalize_conv_text(user_text or "")
1356
+ if any(token in _user for token in ("para que", "para qué", "por que", "por qué")):
1357
+ if _biz:
1358
+ return _lang_text(
1359
+ f"te lo pido para hablar como si ya llevara el chat de {_biz} ||| así la demo te muestra mejor cómo respondería de verdad",
1360
+ f"i ask for it so I can sound like I already handle {_biz}'s chat ||| that way the demo feels real and properly grounded",
1361
+ )
1362
+ return _lang_text(
1363
+ "te lo pido para ubicar el tono, el contexto y cómo tendría que responder ||| apenas me digas el nombre del negocio te muestro la demo bien aterrizada",
1364
+ "i ask for it so I can match the tone, context and way I should reply ||| send me the business name and I’ll make the demo feel real",
1365
+ )
1366
+ if any(token in _user for token in ("eres ia", "eres una ia", "eres bot", "eres un bot", "ai", "bot")):
1367
+ if _owner_already_knows_ai_identity() or _owner_already_got_capability_pitch():
1368
+ return _lang_text(
1369
+ "lo llevaría con el tono de tu negocio, filtrando clientes, respondiendo dudas y moviendo citas sin sonar rígida ||| dime el nombre de tu negocio y te muestro cómo respondería",
1370
+ "I’d handle it with your business tone, filtering customers, answering questions and moving appointments without sounding robotic ||| send me the business name and I’ll show you how I’d reply",
1371
+ )
1372
+ return _lang_text(
1373
+ "sí, soy una IA ||| pero estoy hecha para llevar chats de negocio con tono natural, como si fuera parte del equipo ||| dime el nombre de tu negocio y te muestro cómo respondería",
1374
+ "yes, I’m an AI ||| but I’m built to handle business chats naturally, like someone inside the team ||| send me the business name and I’ll show you how I’d reply",
1375
+ )
1376
+ if _biz:
1377
+ return _lang_text(
1378
+ f"escríbeme algo como cliente de {_biz} y arranco",
1379
+ f"send me something like a real client from {_biz} and I’ll jump in",
1380
+ )
1381
+ if any(
1382
+ token in _user
1383
+ for token in (
1384
+ "me mandaron tu numero", "me mandaron tu número", "me pasaron tu numero",
1385
+ "me pasaron tu número", "que haces", "qué haces", "no entiendo que haces",
1386
+ "no entiendo qué haces", "quien eres", "quién eres",
1387
+ "what is this", "what do you do", "who are you", "i dont understand", "i don't understand",
1388
+ )
1389
+ ):
1390
+ return _lang_text(
1391
+ "Hola, soy Conny 👋 ||| respondo clientes, filtro interesados y ayudo con citas por WhatsApp como si fuera parte del equipo ||| me pasaron tu contacto para hacerte una demostración rápida ||| pásame el nombre de tu negocio y la aterrizo a tu caso",
1392
+ "hi, I’m Conny 👋 ||| I handle customer chats, questions and appointment flow for businesses ||| I’m here to show you a quick live demo ||| to customize it, what’s the business name?",
1393
+ )
1394
+ return _lang_text(
1395
+ "Hola, soy Conny 👋 ||| respondo clientes, filtro interesados y ayudo con citas por WhatsApp como si fuera parte del equipo ||| para mostrarte una demo real, cuéntame, ¿cómo se llama tu negocio?",
1396
+ "hi, I’m Conny 👋 ||| I handle customer chats and appointment flow for businesses ||| to make the demo real, what’s the business name?",
1397
+ )
1398
+
1252
1399
 
1253
1400
  async def _demo_owner_onboarding_reply(*, explain_name: bool = False, force_stage: Optional[str] = None) -> List[str]:
1254
1401
  user_block = text
@@ -1287,11 +1434,11 @@ CÓMO SUENAS: como una persona real de Medellín escribiendo en WhatsApp.
1287
1434
  - Si te hablan en inglés, respondes en inglés perfecto, súper casual y natural de WhatsApp.
1288
1435
 
1289
1436
  TU ESTRATEGIA DE DEMO (no la menciones, solo ejecútala):
1290
- 1. PRIMERO: Si te saludan por primera vez, haz una introducción completa y amable que no confunda a las personas que llegan referidas (ellos a veces no saben qué eres). Explica claramente de qué trata todo esto ANTES de pedir nada. Ej. "¡Hola! soy Conny 👋 Me crearon en Kimika para responder los chats de WhatsApp de los negocios de forma automática, así los dueños descansan. ||| Te pasaron mi número para que te haga una demostración en vivo de cómo trabajaría para tu empresa. ||| Cuéntame, ¿cómo se llama tu negocio o de qué se trata para personalizar la demo?". ¡NUNCA pidas el negocio sin explicar qué eres y para qué estás aquí!
1437
+ 1. PRIMERO: Si te saludan por primera vez, haz una introducción completa y amable que no confunda a las personas que llegan referidas (ellos a veces no saben qué eres). Explica claramente de qué trata todo esto ANTES de pedir nada. Ej. "¡Hola! soy Conny 👋 Me crearon en Innvisor para responder los chats de WhatsApp de los negocios de forma automática, así los dueños descansan. ||| Te pasaron mi número para que te haga una demostración en vivo de cómo trabajaría para tu empresa. ||| Cuéntame, ¿cómo se llama tu negocio o de qué se trata para personalizar la demo?". ¡NUNCA pidas el negocio sin explicar qué eres y para qué estás aquí!
1291
1438
  2. SEGUNDO: cuando te lo den, busca info del negocio y entra en personaje.
1292
1439
  3. TERCERO: invita a que te escriban como si fueran un cliente real.
1293
1440
  4. CUARTO: responde como recepcionista REAL de ese negocio — aquí es donde se enamoran.
1294
- 5. QUINTO: después de 2-3 simulaciones, cierra: "si te gustó, Santiago te cuenta los planes: 3124348669"
1441
+ 5. QUINTO: después de 2-3 simulaciones, cierra: "si te gustó, Santiago te cuenta los planes: 3243699856"
1295
1442
 
1296
1443
  REGLAS DE FORMATO (IMPORTANTÍSIMAS):
1297
1444
  - Escribe de manera ultra natural y fluida, como si chatearas rápido con un amigo.
@@ -1334,7 +1481,7 @@ IDENTIDAD — NUNCA SALGAS DE ESTE PERSONAJE:
1334
1481
  - Cuando no tienes contexto de negocio todavía, igual respondes con seguridad y pides el nombre al final, una sola vez, de forma simple.
1335
1482
 
1336
1483
  REGLAS EXTRA DE ESTA DEMO:
1337
- - si preguntan quién te hizo, quién te creó o cómo tener esto: responde que te hizo Black One. Contacto: 3124348669. Persona: Santiago Rubio
1484
+ - si preguntan quién te hizo, quién te creó o cómo tener esto: responde que te hizo Innvisor. Contacto: 3243699856. Persona: Santiago Rubio
1338
1485
  - si preguntan si aceptas audios, notas de voz, imágenes, PDFs o documentos: responde que sí, cuando el canal lo soporte, puedes transcribir, leer y usar ese contenido
1339
1486
  - si te hacen una pregunta general fuera de contexto, respóndela bien primero y luego vuelve suave a la demo si hace sentido
1340
1487
  - si sospechan estafa o no quieren dar el nombre del negocio, baja la guardia y explica para qué lo pides sin sonar defensiva
@@ -1445,10 +1592,7 @@ EJEMPLOS DE RESPUESTAS BUENAS vs MALAS:
1445
1592
  """,
1446
1593
  )
1447
1594
  if not response:
1448
- response = _lang_text(
1449
- "ay, se me fue el internet por un momento ||| ¿me repites porfa?",
1450
- "oops, my connection dropped for a sec ||| could you say that again?",
1451
- )
1595
+ response = _demo_owner_last_resort(text, explain_name=explain_name)
1452
1596
  response = _normalize_demo_owner_onboarding_response(response)
1453
1597
  _save("user", text)
1454
1598
  return _send(response)
@@ -1588,7 +1732,7 @@ REGLAS ABSOLUTAS - NO ROMPER NUNCA:
1588
1732
 
1589
1733
  RESPUESTAS PARA PREGUNTAS COMUNES:
1590
1734
  - Cuánto cuesta → "El precio lo define el especialista en la valoración. Agenda tu cita y ahí te dicen"
1591
- - Cómo te contrato → "Para eso puedes hablar con Santiago al 3124348669 - él te explica todo"
1735
+ - Cómo te contrato → "Para eso puedes hablar con Santiago al 3243699856 - él te explica todo"
1592
1736
  - Qué servicios → "Tenemos variedad de servicios. Cuál te interesa?"
1593
1737
 
1594
1738
  TONO: Cálido, profesional, como receptionistareal.
@@ -1641,9 +1785,9 @@ TONO: Cálido, profesional, como receptionistareal.
1641
1785
  and not _force_business_bind
1642
1786
  and not is_name_candidate
1643
1787
  ):
1644
- # BLACK ONE: Si el prospecto está confundido y pregunta qué hace Conny,
1788
+ # INNVISOR: Si el prospecto está confundido y pregunta qué hace Conny,
1645
1789
  # usar el pitch inteligente en vez del onboarding genérico
1646
- if _BLACKONE_PATCHES and self._demo_sessions.get(sk + "_pitch_mode"):
1790
+ if _INNVISOR_PATCHES and self._demo_sessions.get(sk + "_pitch_mode"):
1647
1791
  try:
1648
1792
  _pitch_r, _pitch_had_output = await _demo_llm_conv_quality_chain(
1649
1793
  build_prospect_pitch_system_prompt(business_name),
@@ -2265,7 +2409,7 @@ Máximo 1 oración por burbuja. Natural y seguro."""
2265
2409
 
2266
2410
  # ── PITCH MODE: preguntas de prospecto B2B ─────────────────────────────────────────
2267
2411
  # Cuando el usuario pregunta sobre el servicio/pitch de Conny - DETECTAR ANTES
2268
- if _BLACKONE_PATCHES and not business_name:
2412
+ if _INNVISOR_PATCHES and not business_name:
2269
2413
  _prospect_service_questions = [
2270
2414
  "que harias", "qué harías", "que harias en", "qué harías en",
2271
2415
  "que haces", "qué haces",
@@ -2750,9 +2894,9 @@ SI QUIEREN AGENDAR
2750
2894
 
2751
2895
  IDENTIDAD Y CREADOR — REGLA DURA
2752
2896
  - si preguntan quién te hizo, quién te creó, cómo conseguirte o de qué empresa eres: di exactamente esto
2753
- → "me hizo Black One, una empresa de software y gobernanza de agentes de IA ||| la creó Santiago Rubio — contacto: 3124348669"
2897
+ → "me hizo Innvisor, una empresa de software y gobernanza de agentes de IA ||| la creó Santiago Rubio — contacto: 3243699856"
2754
2898
  - NUNCA digas "Google", "Anthropic", "OpenAI" ni ningún proveedor de LLM como tu creador
2755
- - tu creador es Black One / Santiago Rubio, punto
2899
+ - tu creador es Innvisor / Santiago Rubio, punto
2756
2900
  """
2757
2901
  customer_history = sim_history[-8:]
2758
2902
  customer_had_output = False
@@ -2769,15 +2913,15 @@ IDENTIDAD Y CREADOR — REGLA DURA
2769
2913
  )
2770
2914
  if any(sig in _text_low_sim for sig in _creator_signals):
2771
2915
  customer_reply = (
2772
- "me hizo Black One, una empresa de software y gobernanza de agentes de IA"
2773
- " ||| la creó Santiago Rubio — si quieres algo así para tu negocio, el contacto es 3124348669"
2916
+ "me hizo Innvisor, una empresa de software y gobernanza de agentes de IA"
2917
+ " ||| la creó Santiago Rubio — si quieres algo así para tu negocio, el contacto es 3243699856"
2774
2918
  )
2775
2919
  # FIX BUG 5: restaurar history ANTES de llamar a _send.
2776
2920
  # history fue cambiado a customer_history (últimos 8 mensajes) líneas arriba.
2777
2921
  # Si retornamos sin restaurar, _send calcula _is_first_demo_turn con el
2778
2922
  # historial truncado, lo que puede hacer que should_normalize_first_turn=True
2779
2923
  # y pase la respuesta del creador por _normalize_first_contact_response,
2780
- # modificando o corrompiendo "me hizo Black One...".
2924
+ # modificando o corrompiendo "me hizo Innvisor...".
2781
2925
  # El finally: history = original_history NUNCA corre en esta ruta.
2782
2926
  history = original_history
2783
2927
  return _send(customer_reply)
@@ -2796,7 +2940,7 @@ IDENTIDAD Y CREADOR — REGLA DURA
2796
2940
  - si preguntan por cita o siguiente paso, muévelos directo hacia el agendado
2797
2941
  - si expresan miedo, valídalo y responde con seguridad
2798
2942
  - si preguntan si entiendes audios, notas de voz, PDFs, imágenes o documentos: responde que sí, cuando el canal lo permite, puedes transcribirlos o leerlos
2799
- - si preguntan quién te hizo o quién te creó: di "me hizo Black One, una empresa de software y gobernanza de agentes de IA ||| la creó Santiago Rubio — contacto: 3124348669"
2943
+ - si preguntan quién te hizo o quién te creó: di "me hizo Innvisor, una empresa de software y gobernanza de agentes de IA ||| la creó Santiago Rubio — contacto: 3243699856"
2800
2944
  - NUNCA digas que te hizo Google, Anthropic, OpenAI ni ningún proveedor de IA
2801
2945
  - si preguntan por un servicio (botox, relleno, etc.): confirma que sí lo manejan y pregunta qué quieren saber
2802
2946
  """,
@@ -3360,7 +3504,7 @@ OBJECIONES
3360
3504
  "en otro lado" → "claro ||| nosotros somos fabricantes directos, eso cambia precio y garantía"
3361
3505
  "no sé si me quede" → "eso lo vemos en persona — traes la medida y te lo mostramos en el espacio"
3362
3506
  "eres un bot?" → "soy la asesora de {business_name}, trabajo por acá todo el día"
3363
- "cómo tener esto" / "quién te hizo" → "me hizo Black One ||| si quieres algo así, el contacto es 3124348669 con Santiago Rubio"
3507
+ "cómo tener esto" / "quién te hizo" → "me hizo Innvisor ||| si quieres algo así, el contacto es 3243699856 con Santiago Rubio"
3364
3508
  - si preguntan si aceptas audios, notas de voz, imágenes, PDFs o documentos: responde que sí, cuando el canal lo permite, puedes transcribir, leer y usar ese contenido
3365
3509
  - si preguntan algo general o fuera de contexto: respóndelo bien primero y luego vuelve suave al negocio solo si hace sentido
3366
3510
  {v8_build_quality_system_prompt_addon(chat_id=chat_id, archetype="amigable", history=history) if anti_robot_filter else ""}
@@ -1,22 +0,0 @@
1
- {
2
- "version": 1,
3
- "instance_slug": "cl-nica-de-las-am-ricas",
4
- "created_at": "2026-05-21T01:04:23.106236+00:00",
5
- "updated_at": "2026-05-21T01:04:23.106720+00:00",
6
- "assets": [
7
- {
8
- "id": "fbb13771cd3c",
9
- "filename": "business-identity.txt",
10
- "category": "identity",
11
- "source": "system_bootstrap",
12
- "mime_type": "text/plain",
13
- "raw_path": "/home/ubuntu/conny/brand-assets/cl-nica-de-las-am-ricas/raw/business-identity.txt",
14
- "extracted_path": "/home/ubuntu/conny/brand-assets/cl-nica-de-las-am-ricas/processed/business-identity.txt",
15
- "bytes": 852,
16
- "words": 113,
17
- "sha256": "fbb13771cd3cf28d7018411898a350eee11c2720c5358e09ab37dcbb6bd90d13",
18
- "created_at": "2026-05-21T01:04:23.106678+00:00",
19
- "textual": true
20
- }
21
- ]
22
- }
@@ -1,11 +0,0 @@
1
- Negocio: Clínica de las Américas
2
- Conny es la asesora del equipo y debe sonar humana, clara y profesional.
3
- Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
4
- Servicios principales: Botox, Rellenos, Láser, Peeling, Mesoterapia.
5
- Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
6
- Contacto base: (604) 342-1010.
7
- Sitio web: http://clinicalasamericas.lasamericas.com.co/.
8
- Ubicación: Medellín.
9
- Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
10
- Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
11
- Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
@@ -1,11 +0,0 @@
1
- Negocio: Clínica de las Américas
2
- Conny es la asesora del equipo y debe sonar humana, clara y profesional.
3
- Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
4
- Servicios principales: Botox, Rellenos, Láser, Peeling, Mesoterapia.
5
- Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
6
- Contacto base: (604) 342-1010.
7
- Sitio web: http://clinicalasamericas.lasamericas.com.co/.
8
- Ubicación: Medellín.
9
- Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
10
- Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
11
- Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
@@ -1,22 +0,0 @@
1
- {
2
- "version": 1,
3
- "instance_slug": "cl-nica-las-am-ricas",
4
- "created_at": "2026-03-27T06:18:05.570552+00:00",
5
- "updated_at": "2026-03-28T06:54:48.698977+00:00",
6
- "assets": [
7
- {
8
- "id": "51fa867e3611",
9
- "filename": "business-identity.txt",
10
- "category": "identity",
11
- "source": "system_bootstrap",
12
- "mime_type": "text/plain",
13
- "raw_path": "/home/ubuntu/conny/brand-assets/cl-nica-las-am-ricas/raw/business-identity.txt",
14
- "extracted_path": "/home/ubuntu/conny/brand-assets/cl-nica-las-am-ricas/processed/business-identity.txt",
15
- "bytes": 855,
16
- "words": 112,
17
- "sha256": "51fa867e36112be6403f3e7bfb5435bfbd0eee6689dfe1da976de29762483598",
18
- "created_at": "2026-03-28T06:54:48.698934+00:00",
19
- "textual": true
20
- }
21
- ]
22
- }
@@ -1,11 +0,0 @@
1
- Negocio: Clínica Las Américas
2
- Conny es la asesora del equipo y debe sonar humana, clara y profesional.
3
- Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
4
- Servicios principales: Botox, Rellenos, Láser, Peeling, Mesoterapia.
5
- Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
6
- Contacto base: (604) 342-1010.
7
- Sitio web: http://clinicalasamericas.lasamericas.com.co/.
8
- Ubicación: Medellín.
9
- Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
10
- Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
11
- Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
@@ -1,11 +0,0 @@
1
- Negocio: Clínica Las Américas
2
- Conny es la asesora del equipo y debe sonar humana, clara y profesional.
3
- Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
4
- Servicios principales: Botox, Rellenos, Láser, Peeling, Mesoterapia.
5
- Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
6
- Contacto base: (604) 342-1010.
7
- Sitio web: http://clinicalasamericas.lasamericas.com.co/.
8
- Ubicación: Medellín.
9
- Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
10
- Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
11
- Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
@@ -1,22 +0,0 @@
1
- {
2
- "version": 1,
3
- "instance_slug": "conny-demo",
4
- "created_at": "2026-05-17T01:47:19.511278+00:00",
5
- "updated_at": "2026-05-17T01:47:19.516252+00:00",
6
- "assets": [
7
- {
8
- "id": "4c010d49dc7c",
9
- "filename": "business-identity.txt",
10
- "category": "identity",
11
- "source": "system_bootstrap",
12
- "mime_type": "text/plain",
13
- "raw_path": "/home/ubuntu/conny/brand-assets/conny-demo/raw/business-identity.txt",
14
- "extracted_path": "/home/ubuntu/conny/brand-assets/conny-demo/processed/business-identity.txt",
15
- "bytes": 663,
16
- "words": 95,
17
- "sha256": "4c010d49dc7cec1fd19c2f6a4c39b1db5d3a8d823d7b52e5df21d4b1695e48b0",
18
- "created_at": "2026-05-17T01:47:19.516206+00:00",
19
- "textual": true
20
- }
21
- ]
22
- }
@@ -1,7 +0,0 @@
1
- Negocio: Conny Demo
2
- Conny es la asesora del equipo y debe sonar humana, clara y profesional.
3
- Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
4
- Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
5
- Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
6
- Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
7
- Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
@@ -1,7 +0,0 @@
1
- Negocio: Conny Demo
2
- Conny es la asesora del equipo y debe sonar humana, clara y profesional.
3
- Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
4
- Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
5
- Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
6
- Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
7
- Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
package/fix_init.py DELETED
@@ -1,27 +0,0 @@
1
- file_path = "conny.py"
2
- with open(file_path, "r", encoding="utf-8") as f:
3
- content = f.read()
4
-
5
- old_init = """async def init_conny():
6
- \"\"\"Inicializa Conny Ultra.\"\"\"
7
- global conny
8
- conny = ConnyUltra()
9
- await conny.initialize()"""
10
-
11
- new_init = """async def init_conny():
12
- \"\"\"Inicializa Conny Ultra.\"\"\"
13
- global conny
14
- conny = ConnyUltra()
15
- await conny.initialize()
16
-
17
- import src.core.globals as g
18
- global db, llm_engine, auth_engine, mcp_manager
19
- db = g.db
20
- llm_engine = g.llm_engine
21
- auth_engine = getattr(g, "auth_engine", None)
22
- mcp_manager = getattr(g, "mcp_manager", None)
23
- """
24
-
25
- content = content.replace(old_init, new_init)
26
- with open(file_path, "w", encoding="utf-8") as f:
27
- f.write(content)
@@ -1,48 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Verificar que todas las funciones de conversación están presentes."""
3
- import re
4
-
5
- with open('conny-omni.py', 'r') as f:
6
- content = f.read()
7
-
8
- checks = {
9
- 'get_recent_conversations': (r'async def get_recent_conversations\(', 'Obtener conversaciones recientes'),
10
- 'get_active_conversations': (r'async def get_active_conversations\(', 'Obtener conversaciones activas'),
11
- 'format_active_conversations': (r'def format_active_conversations\(', 'Formatear lista de conversaciones'),
12
- 'format_conversation_detail': (r'def format_conversation_detail\(', 'Formatear detalle de conversación'),
13
- '/conversations handler': (r"text\.lower\(\)\.startswith\(\"/conversations\"\)", 'Manejador /conversations'),
14
- '/enter handler': (r'text\.startswith\(\"/enter ', 'Manejador /enter'),
15
- '/list handler': (r'text\.lower\(\) == \"/list\"', 'Manejador /list'),
16
- 'Natural intent detection': (r'conversation_keywords = \[', 'Detección de intención natural'),
17
- 'muéstrame/dame detection': (r"any\(kw in text_lower for kw in \[\"muéstrame\",", 'Detección muéstrame/dame'),
18
- 'quién está detection': (r"any\(kw in text_lower for kw in \[\"quién\", \"quien\"", 'Detección quién está'),
19
- 'System prompt update': (r'"También eres GESTORA DE CONVERSACIONES"', 'System prompt actualizado'),
20
- 'Help text update': (r"\*Gestión de Conversaciones:\*", 'Help text actualizado'),
21
- }
22
-
23
- print("=" * 70)
24
- print("✅ VERIFICACIÓN: Conny Omni v2.1 - Conversation Features")
25
- print("=" * 70)
26
- print()
27
-
28
- all_ok = True
29
- for name, (pattern, desc) in checks.items():
30
- if re.search(pattern, content, re.MULTILINE):
31
- print(f"✅ {name:30} - {desc}")
32
- else:
33
- print(f"❌ {name:30} - {desc}")
34
- all_ok = False
35
-
36
- print()
37
- print("=" * 70)
38
- if all_ok:
39
- print("✅ TODAS LAS VERIFICACIONES PASARON")
40
- print("🎉 Omni v2.1 está LISTO para producción")
41
- else:
42
- print("❌ ALGUNAS VERIFICACIONES FALLARON")
43
-
44
- print("=" * 70)
45
-
46
- # Contar líneas de código nuevo
47
- conv_section = content[content.find('async def get_recent_conversations'):content.find('# ══════════════════════════════════════════════════════════════════════════════\n# NOTIFICACIONES')]
48
- print(f"\nLíneas de código para conversaciones: ~{len(conv_section.splitlines())} líneas")