Skip to content

fix(handoff): un solo ticket abierto por conversación (ticket #1EB810) - #79

Draft
santmun wants to merge 1 commit into
mainfrom
cursor/fix-handoff-ticket-dup-3f00
Draft

santmun wants to merge 1 commit into
mainfrom
cursor/fix-handoff-ticket-dup-3f00

Conversation

@santmun

@santmun santmun commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Qué cambia

El traspaso a humano deja de abrir un ticket nuevo en cada mensaje de la misma conversación. Si ya hay un ticket sin resolver colgado de conversations.open_ticket_id, se le añade el contexto y no se vuelve a avisar al dueño.

Por qué

Ticket de soporte #1EB810 (plantilla v1.0.77). Un cliente pidió hablar con una persona y preguntó dos veces más por la misma llamada: 3 tickets open, 3 avisos, y open_ticket_id quedó apuntando solo al último. Los anteriores seguían contando como pendientes en el panel.

En este repo el mismo defecto está en handoffHumanTool (src/tools/handoffHuman.ts): cada ejecución llama tickets.create() y después convs.setOpenTicket(), que es un UPDATE ciego. No existe createHandoffTicket() (ese nombre es de la plantilla publicada).

Los 5 bugs del ticket

1. Ticket nuevo por mensaje — arreglado aquí

  • Causa: no había lectura de open_ticket_id antes de crear. setOpenTicket pisa el puntero y deja los tickets viejos abiertos pero desenganchados.
  • Arreglo: si el puntero apunta a un ticket con status distinto de resolved (incluye in_progress), TicketsRepo.appendSummary() añade [motivo] resumen y la tool regresa ese id, sin email ni notifyOwner. Un ticket ya resuelto sí abre uno nuevo y vuelve a avisar.
  • Test: test/tools/handoffHuman.test.ts — tres traspasos seguidos dejan 1 ticket y 1 aviso de Telegram; después de resolve() el siguiente traspaso abre otro y avisa otra vez. Antes del arreglo el segundo id no coincidía con el primero.

2. Blindaje rompe el modo espejo — no está en este repo

src/blindaje/verify.ts, src/idioma.ts y test/idioma.test.ts no existen en santmun/forja. No se puede reproducir ni cubrir con un test aquí.

La pista del miembro sí cuadra con la plantilla que sirve forjabot update (verifiqué el árbol público de esa línea, v1.0.74, mismas líneas que cita el ticket en v1.0.77): verify.ts:394 llama safeConfirmReply(env.BOT_LANGUAGE, opts.channel) con las cadenas fijas de SAFE_REPLY_BY_LANG / SAFE_REPLY_WEB_BY_LANG. idiomaEfectivo() en modo espejo devuelve a propósito el idioma base (test/idioma.test.ts:86). La frase de seguridad no pasa por el modelo, así que no espeja.

Decisión: el arreglo tiene que caer en el checkout que empaqueta horizontes-license-server/scripts/publish.mjs (../horizontes-bot-template). En GitHub, Horizontes-IA/horizontes-bot-template sigue en el árbol de julio 2026 (package 0.1.0, sin src/blindaje). La v1.0.77 publicada no está en este remoto. No publiqué artifact ni subí versión.

Arreglo sugerido, sin tocar el pin de {{LANGUAGE}}: si espejaAlCliente(env), detectar el idioma del último mensaje del cliente y pasarlo a safeConfirmReply().

3. Zoom de iOS al enfocar el input — no está en este repo

No hay src/web/temas.ts. En la plantilla publicada el textarea lleva font-size:14.5px (línea 214) y el @media (max-width:420px) no lo sube. iOS Safari hace zoom por debajo de 16px y no lo devuelve.

Decisión: en ese CSS, dentro del media query de 420px, subir el textarea a 16px para no mover el diseño de escritorio.

4. Autofoco abre el teclado en el celular — no está en este repo

No hay src/web/script.ts. En la plantilla, abrir() llama ta.focus() siempre (línea 176). No hay matchMedia.

Decisión: enfocar solo si window.matchMedia("(min-width:421px)").matches, el mismo corte que ya usa el CSS.

5. Asteriscos en vez de negritas en el widget — no está en este repo

No hay widget web ni src/owner/leadmagnet.ts. Aquí el <style_guide> ya pide texto plano y stripMarkdown() (src/replies/chunker.ts) quita **negrita** antes de enviar; eso ya tiene test en test/replies/chunker.test.ts.

En la plantilla publicada el prompt todavía permite markdown y agrega() pinta con textContent (script.ts:160). renderMarkdown() en leadmagnet.ts ya escapa HTML y cubre negritas, itálicas, listas y links.

Decisión: reutilizar renderMarkdown() en el widget (escapado, sin HTML crudo) en vez de solo prohibir markdown en el prompt. El cliente ve la negrita; el escape evita XSS. No aplicar innerHTML del texto del modelo sin ese escape.

Cómo lo probaste

  • pnpm test pasa (79 archivos, 564 pruebas)
  • pnpm typecheck limpio
  • (si aplica) probado contra un bot real — no: el bug 1 se cubrió con D1 de Miniflare; los bugs 2–5 no viven en este árbol

El test de reutilización falló antes del cambio (expected el id del primer ticket, received uno nuevo) y pasa después.

Checklist

  • No toqué la carpeta member/ (config de cada quien)
  • El PR es de un solo tema (enfocado y chico) — el diff solo toca el handoff; 2–5 quedan documentados porque el código no está en este repo
  • Si tu agente (Claude) hizo el PR, revisaste el diff tú mismo antes de abrirlo
  • No hay secrets ni API keys en el código

No se publicó a npm, no se bumpeó versión y no se mergeó. Los miembros en v1.0.77 no reciben este arreglo con forjabot update hasta que el mismo guard se copie al checkout de la plantilla y se publique un artifact nuevo.

Open in Web Open in Cursor 

…o abierto

El traspaso llamaba tickets.create() y setOpenTicket() en cada mensaje, así
que el mismo pedido dejaba varios tickets open y un aviso al dueño por cada
uno. Si open_ticket_id apunta a un ticket sin resolver, se le añade el
contexto y no se vuelve a notificar.

Co-authored-by: santmun <santmun@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 8, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants