# Charlie · Capa IA transversal de AnimalCharlie.

Charlie es la capa IA viva del ERP AnimalCharlie. No es solo un chat: calcula contexto de cada módulo, detecta alertas, propone próximos pasos, deja notas trazables y puede preparar acciones de trabajo. Las escrituras nunca se hacen directamente contra los módulos; siempre pasan por la capa segura `/api/charlie/*` y requieren confirmación cuando modifican datos.

## Carcasa Y Navegación

La cabecera común muestra `Conversación con Charlie`, el estado humano del agente y el menú `Más`. La región principal es siempre conversación y compositor; `Detalles de la respuesta` y `Audio y herramientas` permanecen plegados hasta que el usuario los solicita. OpenAPI, MCPs, documentación, actualización y modo agente siguen disponibles en `Más`, sin competir con el envío de mensajes.

Charlie declara un layout `stack`: chat como `content` y herramientas como región secundaria. La acción principal continúa siendo `Enviar` dentro del compositor, porque es donde el usuario entiende su efecto.

## Contrato Base

- Especificación OpenAPI: `/api/openapi.json`, protegida con `charlie.read` o `charlie.execute`. Desde la UI se abre con el botón `OpenAPI`, que reutiliza la sesión autenticada y evita abrir una pestaña sin cabecera `Authorization`.
- Catálogo de acciones: `/api/charlie/actions`
- Catálogo MCP de módulos: `GET /api/charlie/mcps`
- Ficha MCP de módulo: `GET /api/charlie/mcps/{module}`
- Inteligencia ambiental del módulo activo: `GET /api/charlie/ambient?module=<modulo>`
- Notas y detalles de Charlie: `POST /api/charlie/notes`
- Mensajería del agente: `POST /api/charlie/message`
- Transcripción de audio: `POST /api/charlie/transcribe-audio`
- Ejecución confirmada: `POST /api/charlie/execute`
- Estado Charlie IA V2 web: `GET /api/charlie/v2/status`
- Auditoría: todas las ejecuciones quedan registradas como `charlie_action_execute`
- Editor IA: acción `improve_report_text` para preparar propuestas de mejora de informes sin aplicarlas automáticamente.
- MCP por módulos: acciones `list_module_mcps`, `module_mcp_context` y `module_control_plan`, documentadas en `docs/CHARLIE_MCP.md`.
- Proveedor LLM: OpenAI-compatible por defecto si hay clave, apuntando a `http://10.20.20.56:4000/v1`
- Proveedor de transcripción: Whisper local, por defecto modelo `small`

Todas las rutas, salvo `/api/health` y `/api/login`, usan cabecera de autorizacion con esquema Bearer; no documentar ni registrar tokens reales.

`POST /api/charlie/message` es una superficie de consulta y acepta `charlie.read` o `charlie.execute`; no exige permiso de ejecución para preguntar. Charlie clasifica cada turno como `read`, `clarify` o `write`. Una lectura nunca abre confirmación ni crea borradores. Una aclaración pregunta únicamente el dato mínimo que falta y nunca convierte esa ausencia en una tarea. Solo una petición explícita de escritura puede devolver un único borrador, siempre limitado al catálogo permitido para el rol y pendiente de una única confirmación visible. La escritura real continúa exclusivamente en `POST /api/charlie/execute`, que mantiene `charlie.execute` y la confirmación explícita.

## Charlie IA V2 En La Web

La vista `#charlie` consulta solo al backend de AnimalCharlie mediante `GET /api/charlie/v2/status`. El navegador no llama a LiteLLM, Kimi ni a ningún endpoint externo de IA. El endpoint devuelve metadatos seguros: disponibilidad, proveedor mediado por backend, agente previsto para el módulo activo, política de confirmación, acciones confirmables y secciones separadas de datos relevantes, inferencias, próximas acciones y evidencia.

La UI prioriza una conversación a ancho completo. Al entrar ofrece tres ejemplos breves —`Prioridades de hoy`, `Resumen operativo` y `Ver agentes`— que desaparecen tras la primera consulta. El estado visible usa lenguaje humano como `Charlie listo`, `Pensando` o `Respuesta lista`; el formulario propone `Pregunta, compara datos o pide un borrador` y evita exponer proveedor, modelo u orquestador en la ruta principal.

OpenAPI, MCPs, documentación, actualización de acciones y `Modo agente` viven en el menú `Más`. Debajo del chat permanecen cerrados por defecto `Detalles de la respuesta`, que reúne datos, próximos pasos y actividad, y `Audio y herramientas`, que contiene dictado, creación de informes y el catálogo completo de acciones. Al abrir uno, ambos accesos se apilan y el contenido solicitado usa todo el ancho disponible. La información de proveedor, red, conversación, inferencias y evidencia queda un nivel más adentro bajo `Información técnica` para no saturar la vista diaria.

Este diseño se valida únicamente en las superficies soportadas: PC, Galaxy Tab A9+ a `1280x800` e iPad Air a `1180x820` y `820x1180`. En tablet los dos plegables inferiores se apilan y la conversación conserva la prioridad vertical; los teléfonos no forman parte del alcance.

La pagina completa `#charlie` muestra además `Conversación entre agentes` dentro de `#charlieV2Console`. Ese bloque pinta qué se dijeron el orquestador y los agentes por módulo con etapas `interpreta`, `consulta`, `responde`, `sintetiza` y `propone`, incluyendo agente, módulo, estado y extracto del mensaje. La UI consume `agentConversation`, `agentMessages` o `conversation` cuando llegan del backend; si no existen, usa `trace.agentConversation`, `trace.messages`, `routing.steps` o `routing.hops`; y, como fallback visible, deriva una simulación visual a partir de datos backend reales (`moduleAgents`, `moduleAgentResults`, `reply`, `actionDraft` y `proposedActions`). La superficie principal usa nombres humanos (`Charlie orquestador`, `Agente de Calidad lechera`); los IDs `hermes-*` quedan en detalles avanzados. En consultas de calidad lechera, secado o datos históricos deben participar `hermes-module-milk_quality` y `hermes-module-datahub` cuando el routing los seleccione.

Si `CHARLIE_IA_V2_ENABLED` no está activado o falta la configuración backend de Hermes Charlie (`HERMES_CHARLIE_BASE_URL` en entorno, sin exponerla al cliente), el panel muestra `No disponible`. En el despliegue operativo Charlie IA V2 es el runtime principal del chat; las respuestas de `/api/charlie/message` y `/api/charlie/execute` incluyen `v2.sections` para clasificar datos, inferencias, acciones y evidencia.

El backend refuerza el enrutado semántico con señales explícitas de dominio (`milk_quality`, `billing`, `sige`, `clients`, `medicines`) para que las preguntas naturales caigan en el agente correcto aunque el módulo activo sea ambiguo. Cuando Hermes Charlie devuelve una respuesta sin usar el contexto seguro, AnimalCharlie genera una síntesis verificable a partir de `safeResults`, métricas, totales y resultados de agentes de módulo. Esa degradación respeta también `read`, `clarify` y `write`: no añade una sección de acciones a una lectura ni solicita confirmación por una alerta encontrada.

Las acciones propuestas siguen siendo borradores hasta que el usuario confirma en AnimalCharlie. El panel de vista previa identifica el módulo y el registro objetivo, enumera los campos que se guardarán, compara valores actuales y nuevos cuando el backend los aporta, avisa de datos pendientes y explica por qué la escritura necesita confirmación. Permite `Confirmar y guardar` o `Cancelar`; el JSON completo queda bajo `Ver detalle técnico`. Cancelar descarta el borrador en la UI y no llama a `/api/charlie/execute`.

Las consultas de disponibilidad se serializan con un identificador monotono. Solo la petición más reciente, para la misma sesión y el mismo módulo, puede actualizar el panel y los estados del chat/popup. Cancelar un borrador invalida cualquier comprobación iniciada antes, por lo que una respuesta tardía no puede sustituir `Acción cancelada · sin cambios` ni reintroducir una propuesta pendiente. El verificador de Charlie fuerza esta carrera y espera el estado estable; también registra URL, código HTTP y tipo de recurso de cada carga fallida.

## MCP Interno De Módulos

Charlie ya ve cada módulo activo de Animal Charlie como un MCP interno. El catálogo vive en `ANIMAL_CHARLIE_MCP_MODULE_DEFS` dentro de `server.py` y se publica por `GET /api/charlie/mcps`. Cada ficha declara `id`, `module`, `hash`, alias de lenguaje natural, tipos de entidad, recursos `animal-charlie://...`, herramientas ejecutables y política de confirmación.

Módulos publicados: inicio, explotaciones, certificación BEA, calidad lechera CMT, SIGE, clientes, facturación, proveedores, Genaro, Vetiquín, Charlie, calendario, proyectos, personal, usuarios, acciones, mejoras, evidencias, normativa, integraciones, DataHub, Wiki y archivo.

Las herramientas MCP no saltan la seguridad de Charlie: todas apuntan a acciones de `/api/charlie/execute`; las lecturas pueden ejecutarse directamente y las escrituras mantienen `requiresConfirmation: true`. La acción `list_module_mcps` lista el catálogo completo, `module_mcp_context` devuelve la ficha MCP de un módulo junto con su contexto ambiental actual y `module_control_plan` resume qué módulos ya tienen control de escritura confirmable y qué herramientas faltan por añadir.

Cada MCP incluye `aiInstructions`: instrucciones breves para que el modelo entienda qué es ese módulo, cuándo usarlo y qué límites debe respetar. En cada llamada LLM, Charlie carga `animalCharlieContext.mcpContext` con:

- `allModules`: catálogo resumido de los MCP activos, sus instrucciones IA, recursos y herramientas seguras.
- `activeModule`: MCP del módulo visual activo si el frontend lo aporta.
- `relevantModules`: MCP derivados del módulo activo, del resultado ejecutado o de las tarjetas devueltas.

El prompt de sistema obliga a usar ese contexto antes de responder, elegir el MCP correcto y no inventar recursos ni herramientas.

El modo agente se expone como acción `agent_runbook`. Recibe un objetivo, un módulo inicial opcional y límites de lectura; selecciona MCPs relevantes, consulta contexto ambiental y acciones de lectura seguras, y devuelve `steps`, `items` y `nextActions`. Antes de devolver el resultado deduplica `items` por módulo/registro para no repetir hallazgos idénticos. No transforma métricas, alertas ni datos ausentes en operaciones por defecto: solo incluye un `actionDraft` cuando el objetivo contiene una escritura explícita compatible, y nunca más de uno.

Guía de desarrollo para añadir más módulos MCP: `docs/CHARLIE_MCP.md`.

## Contexto En Segundo Plano Y Sugerencias Puntuales

La UI no muestra a Charlie como panel permanente en todo el ERP. Al cambiar de módulo, el navegador llama a `GET /api/charlie/ambient?module=<modulo>` y mantiene el resultado en memoria para que la IA trabaje de fondo. Cada petición queda ligada al módulo, usuario y secuencia que la inició: una respuesta antigua no puede sobrescribir el contexto después de navegar o seleccionar otro registro. Al comenzar una navegación se retira de inmediato la sugerencia o ficha del módulo anterior, sin mantener información antigua mientras llega la nueva respuesta. La bandeja `#charlieAmbientPanel` está oculta por defecto y puede aparecer cuando hay una señal accionable del módulo o una nota trazable de un registro que el usuario haya seleccionado expresamente. Los registros cargados automáticamente para completar un master-detail no se convierten en contexto de Charlie.

En PC y tablet la bandeja se acopla a una fila propia del workspace, entre el módulo y la barra de estado, con su altura real de una sola línea. No usa posición fija, no cubre tablas, controles, tarjetas de calidad del ERP o paginación y la carcasa no reserva una franja gris mayor que la sugerencia. El cierre mantiene un objetivo táctil accesible y guarda una clave estable por usuario en `sessionStorage`: la misma sugerencia de módulo o entidad no reaparece durante esa sesión aunque el contexto se recalcule, pero el descarte de una cuenta no oculta avisos para otra cuenta usada en la misma pestaña.

El chat diario de Charlie vive en `#charliePopupShell`, un popup flotante abierto desde `#charlieFloatingBtn` o los botones `data-charlie-prompt`. El lanzador compacto está dentro de `.topbar-global-controls`, inmediatamente antes del buscador global, para no reservar espacio sobre el contenido ni tapar controles en la esquina inferior. El mismo botón alterna entre abrir y cerrar, actualiza `aria-expanded` y cambia visualmente de la imagen de Charlie a una `×`; solo se oculta dentro del módulo completo `#charlie`. El encabezado del dashboard no repite un segundo botón textual de Charlie. La cabecera del popup reserva una fila superior independiente para el botón textual `Cerrar`, de modo que estado, Canvas o `Abrir completo` nunca pueden empujarlo ni recortarlo. `Cerrar`, el lanzador activo y `Escape` cierran el popup y devuelven el foco al control que lo abrió. El popup queda separado físicamente del lanzador y usa una capa global superior a los paneles internos de los módulos, por lo que ninguna ficha, tabla o bandeja puede interceptar sus controles. `Alt+C` abre el módulo completo de Charlie. Ambas superficies comparten la misma conversación real, usan el mismo endpoint `POST /api/charlie/message` y no cambian el módulo activo mientras se pregunta desde el popup.

El navegador crea un `conversationId` por usuario y pestaña, lo conserva en `sessionStorage` y lo reutiliza al cerrar o abrir el popup, cambiar al módulo completo o recargar esa pestaña. El backend valida la propiedad de la conversación, persiste los intercambios en `charlie_conversations` y `charlie_conversation_messages`, recupera únicamente los turnos recientes dentro de límites de mensajes y caracteres y los entrega a Hermes antes del mensaje actual. Una conversación nunca puede reutilizarse entre usuarios aunque se fuerce el identificador. Cerrar la pestaña o cerrar sesión inicia una conversación nueva; el historial almacenado queda aislado en backend para trazabilidad, sin exponerse a otra sesión.

Cuando el proveedor LLM queda en segundo plano, la UI no imprime una respuesta operativa local como atajo. La primera línea de la pregunta se convierte en un único prado: sus letras reales brotan en verde como hierba y desaparecen al ritmo de las mordidas. Charlie y todos los agentes candidatos visibles se reparten la frase en tramos, avanzan y comen simultáneamente; no existe otra vaca, otro prado ni otra manada dentro de la burbuja de progreso. La burbuja de espera se limita a la ruta operativa de cinco fases —contexto, selección de especialistas, consulta, síntesis y revisión— con porcentaje estimado, tiempo transcurrido y una horquilla conservadora de tiempo restante. Esa estimación se detiene en el 94 % mientras el servidor sigue trabajando y cambia a `más de lo habitual` cuando ya no existe una previsión fiable; nunca simula streaming ni afirma que el backend ha terminado antes de recibir la respuesta.

Charlie permanece como la vaca frisona principal dentro de ese mismo prado. Cada módulo candidato añade una vaca SVG distinta con raza y accesorio estables por identificador de agente: Jersey, Parda alpina, Angus, Rubia gallega o Charolesa con sombrero de paja, gorra, gafas, auriculares o flor. Una etiqueta breve acompaña a cada animal (`Charlie`, `Calidad`, `Almacén`, etc.) y el tooltip conserva el nombre completo del agente, la raza, el accesorio y su condición de candidato. Durante la espera el prado se rotula como `Agentes candidatos`, porque la selección real solo se conoce al terminar la llamada; la ruta de agentes de la respuesta final continúa mostrando únicamente la participación confirmada por `agentConversation`. El ciclo animal coordina pasos alternos, inclinación de cabeza, mandíbula, hierba en la boca, campana, sombra y polvo de pezuñas; el parpadeo, las orejas y la cola aportan movimiento orgánico sin alterar el ritmo de lectura. Los colores naturales de cada raza son independientes del tema. El estado textual vive en una región `aria-live` y `prefers-reduced-motion` conserva la composición completa sin animación. La escena se sustituye por la respuesta final de Hermes Charlie; si el proveedor falla o no termina, el chat muestra el error seguro o la síntesis V2 basada en contexto real, nunca una respuesta inventada.

El estado operativo del popup (`#charliePopupState`) recorta textos largos en una sola línea para no romper la cabecera en PC, Galaxy Tab A9+ o iPad Air. El módulo completo se conserva para OpenAPI, audio, catálogo de acciones y confirmaciones de escritura. Los iconos de entrada de Charlie usan una animación ligera de movimiento y respetan `prefers-reduced-motion`. Los teléfonos y el breakpoint inferior a `720px` no forman parte del alcance soportado.

El popup puede crecer sin recargar la UI a `#charlieCanvasShell`, una ampliación visual del chat que se abre con `clip-path: circle()` desde el punto de origen de Charlie. No es un módulo de gestión ni un visor técnico: es el espacio donde la IA desarrolla una explicación con más superficie y puede combinar texto, documentos, tablas, gráficas, indicadores, comparativas, imágenes, código, listas, cronologías, organigramas o diagramas de flujo. En PC la vista visual permanece junto al chat para poder seguir preguntando; en tablet vertical Canvas ocupa la superficie disponible y `Seguir en chat` devuelve el foco a la conversación sin superponer ambos paneles. Cada respuesta muestra además una `Ruta de agentes` reutilizable tanto en el chat como en Canvas: conserva el orden de participación, agrupa las fases de cada especialista en un único icono y permite que Charlie aparezca al principio y al final como coordinador y sintetizador. Al pasar el ratón o enfocar un icono se muestran el nombre humano, las fases, el módulo y el identificador técnico `hermes-*`.

Charlie abre Canvas automáticamente cuando el usuario pide visualizar, mostrar, comparar, abrir o resaltar contenido, cuando la respuesta trae comandos `::canvas`/`charlie-canvas` o cuando existe una respuesta normal solicitada expresamente en formato visual. Las preguntas corrientes siguen en el chat. Mientras no exista un modelo previo y el LLM esté pendiente, el botón `Canvas` queda deshabilitado; al terminar vuelve a estar disponible. La vista muestra una sola vez la explicación limpia y coloca debajo los bloques visuales, sin repetir una lista técnica de artefactos. Los archivos, registros y referencias quedan en un cajón secundario `Fuentes y archivos`, cerrado por defecto. La cabecera mantiene como opciones secundarias copiar la respuesta o descargar Markdown.

El parser conserva JSON flexible (`json/js/txt`) con objetos `type/kind`, infiere listas, cronologías o secuencias únicamente cuando encuentra contenido suficiente en la respuesta y no inventa plantillas operativas genéricas cuando faltan datos. En el chat, las métricas e inferencias que Charlie ya explica en el texto y en la ruta de agentes no se repiten como tarjetas de resultado ni generan botones `Canvas`/`Abrir`; las tarjetas quedan reservadas para registros reales y agrupan como máximo una apertura por módulo. Una métrica informativa igual a cero tampoco origina una acción confirmable: los borradores operativos se reservan para importes, recuentos o alertas que realmente requieran intervención. Los organigramas aceptan nodos planos con `parent` o árboles anidados; los diagramas aceptan nodos y relaciones, eliminan duplicados, descartan enlaces huérfanos y rompen ciclos jerárquicos para que un payload imperfecto no bloquee el Lienzo. Desde Certificación, `#openReportCanvasBtn` convierte el documento activo en indicadores, gráfica por secciones, tabla de estructura y extracto; en Personal, `staff_contracts_overview` puede devolver contratos como indicadores, gráfica y tabla en la misma vista visual.

El renderizador reconoce comandos visuales incluidos en la respuesta:

- `::canvas image url="https://..." title="Foto"` para imágenes.
- `::canvas file url="https://..." title="Documento"` para archivos o documentos.
- `::canvas invoice number="F-001" total="120.00" client="Cliente"` para facturas.
- `::canvas highlight text="Dato a resaltar"` para marcas visibles.
- `::canvas animation title="Proceso"` para microanimaciones dentro del panel.
- `::canvas checklist items="[ ] Llamar cliente|[x] Enviar informe"` para listas de seguimiento.
- `::canvas timeline events="2026-05-23|Visita|Pendiente de cierre"` para cronologías operativas.
- `{"type":"orgchart","title":"Equipo","nodes":[{"id":"direccion","label":"Dirección"},{"id":"campo","label":"Equipo de campo","parent":"direccion"}]}` para organigramas.
- `{"type":"flowchart","title":"Aprobación","nodes":[{"id":"inicio","label":"Solicitud","shape":"start"},{"id":"fin","label":"Aprobado","shape":"end"}],"edges":[{"from":"inicio","to":"fin"}]}` para procesos y mapas de relaciones.
- Bloques JSON con etiqueta `charlie-canvas` y uno o varios objetos equivalentes.
- Bloques JSON flexibles en vallas `json`, `js` o `txt`, e incluso objetos sueltos dentro de la respuesta, siempre que incluyan `type` o `kind`; el canvas los rescata como artefactos y limpia del Markdown visible el payload crudo ya renderizado.

El backend entrega `canvasProtocol` tanto al proveedor integrado como al runtime Hermes Charlie. Para una petición visual, el agente debe responder con un único bloque `charlie-canvas`; quedan prohibidos árboles ASCII, Mermaid, Graphviz, HTML y SVG. Si un modelo devuelve un organigrama de agentes en un formato inválido, el piloto lo rechaza y puede reconstruir de forma determinista el bloque `orgchart` usando únicamente el orquestador y los agentes presentes en el contexto seguro.

La bandeja puntual renderiza:

- Una recomendación o alerta concreta.
- El registro relacionado solo cuando existe una selección explícita y la sugerencia procede de esa entidad.
- Uno o dos controles del mismo ámbito: controles de módulo para alertas ambientales y controles de entidad para notas del registro seleccionado.

El resto del contexto, métricas y notas se siguen calculando en segundo plano para alimentar el módulo Charlie, el registro activo y las acciones seguras.

Módulos cubiertos por `charlie_ambient_context`: inicio/dashboard, explotaciones, clientes, facturación, Genaro, calendario, proyectos, personal, usuarios, SIGE, certificación BEA, calidad lechera, Vetiquín, DataHub, acciones, evidencias, normativa, integraciones, archivo y Charlie.

Ejemplos de cálculos:

- Facturación: borradores, vencidas, saldo vencido, planes recurrentes listos y total pendiente.
- Genaro: plantillas, documentos administrativos, revisiones pendientes y documentos listos para firma; la conversión Word y la revisión ortográfica usan `hermes-module-genaro` sin aplicar cambios automáticamente.
- Clientes: clientes activos, explotaciones, riesgo alto y saldo pendiente agregado.
- Acciones: abiertas, vencidas y alta prioridad.
- Calidad lechera: informes CMT, vacas revisadas, positivas y CMT 4+.
- Vetiquín: recetas vinculadas, pendientes de revisión por estado, avisos, antibióticos, falta de número de registro o tiempos de espera, revisiones documentadas en `metadata.reviewedAt`, retiradas de leche/carne que requieren atención, y recetas con indicador antibiótico en datos CIMAVet. Al guardar, archivar o cerrar una revisión desde Vetiquín, la UI refresca entidad activa y contexto ambiental para que la bandeja de Charlie use la cola real actualizada.
- Integraciones: conectores disponibles, configurados y consultas runtime.
- BEA: informes, borradores, aprobados y comentarios abiertos.

`POST /api/charlie/notes` crea una nota trazable en `charlie_notes` con `module`, `entity_type`, `entity_id`, `title`, `body`, `kind`, `status`, `source`, `created_at` y `created_by`. La acción equivalente del catálogo es `create_note`, requiere confirmación y deja auditoría `charlie_note_create`.

Cada respuesta ambiental incluye `controls`, una lista de controles operativos que la UI renderiza dentro del módulo activo:

- `kind: "prompt"` abre el chat flotante de Charlie con el análisis del módulo.
- `kind: "note"` guarda la nota sugerida para el módulo.
- `kind: "execute"` lanza una acción segura de `/api/charlie/execute`. Si `requiresConfirmation` es `true`, la UI abre el panel de confirmación de Charlie antes de escribir.

Todos los módulos reciben al menos análisis IA, guardado de nota y recalculo/lectura contextual. Los módulos con acciones específicas añaden accesos directos, por ejemplo `prioritize_work`, `client_followup_plan`, `billing_risk_report`, `staff_contracts_overview`, `medicine_review_queue`, `medicine_withdrawal_watchlist`, `data_quality_audit`, `list_today_calendar`, `module_control_plan` o `dashboard_summary`.

## Ficha Activa De Registro

Además del contexto de módulo, el ERP consulta `GET /api/charlie/entity?module=<modulo>&entityType=<tipo>&entityId=<id>` para mantener el registro activo en segundo plano. El contexto alimenta `#erpRecordPill`, las sugerencias puntuales de `#charlieAmbientPanel` y las notas ligadas al registro cuando el usuario selecciona un cliente, factura, explotación, SIGE, evento o informe.

La respuesta incluye:

- `title`, `subtitle`, `entityType` y `entityId` para identificar el registro activo.
- `facts`, una lista corta de campos clave normalizados para lectura rápida.
- `notes`, filtradas por `module`, `entity_type` y `entity_id`.
- `controls`, con los mismos tipos que la capa ambiental: `prompt`, `note` y `execute`.

Los módulos deben usar identificadores estables al seleccionar registros. Cuando un módulo cambia de registro activo debe llamar a `refreshCharlieEntity(<modulo>)` o actualizar su estado interno antes de que la capa de fondo recalcule. Las listas de proyectos, personal, acciones, evidencias, normativa y backups marcan la fila activa para que Charlie siga la navegación real del usuario sin aparecer como bloque global. Los enlaces relacionados desde Clientes transfieren expresamente la explotación, proyecto, evento, SIGE, informe BEA o factura al módulo de destino; no dependen del primer registro cargado allí. Si la entidad seleccionada desaparece, caduca o devuelve `404`, la selección se elimina y Charlie vuelve al contexto general del módulo. Las notas de ficha se guardan en `charlie_notes` con `entityType` y `entityId`, por lo que no sustituyen las notas generales del módulo.

Ejemplo:

```http
GET /api/charlie/ambient?module=billing
Authorization: Bearer <token>
```

```http
POST /api/charlie/notes
Authorization: Bearer <token>
Content-Type: application/json

{
  "module": "billing",
  "title": "Nota de seguimiento en Facturación",
  "body": "Facturas vencidas: 3; Total pendiente: 1280.50",
  "kind": "calculation"
}
```

## Proveedor LLM auxiliar compartido

El chat principal usa exclusivamente Hermes Charlie IA V2, mediado por backend. AnimalCharlie conserva un cliente OpenAI-compatible separado para tareas auxiliares que aún lo consumen, como el análisis de adjuntos SIGE y la mejora de informes. Este cliente no genera la conversación de Charlie, no aparece como su runtime principal y nunca se llama desde el navegador.

Kimi queda disponible como modelo auxiliar preferente dentro del servidor local de IA. La clave no se guarda en código ni debe subirse a Git.

Orden de configuración:

1. Variable de entorno `KIMI_API_KEY`, si se quiere separar la clave.
2. Variables de entorno `OPENAI_COMPAT_API_KEY` u `OPENAI_API_KEY`, que son las usadas por el servidor local.
3. Archivo local `data/kimi-api-key.txt`, si se quiere separar la clave, o `data/openai-compatible-api-key.txt` con permisos `600`.

Opciones:

- `CHARLIE_LLM_PROVIDER`: `kimi`, `openai-compatible` o `minimax`. Si no se define, el cliente auxiliar usa Kimi cuando encuentra la clave compatible local y cae a OpenAI-compatible o MiniMax si se fuerza otro proveedor.
- `KIMI_BASE_URL`: por defecto reutiliza `OPENAI_COMPAT_BASE_URL`, es decir `http://10.20.20.56:4000/v1` en el despliegue local.
- `KIMI_MODEL`: por defecto `kimi-k2.6:cloud`, modelo detectado en el servidor local. Si se define como `auto`, Charlie consulta `GET /models` y prefiere `kimi-k2.6:cloud`, `kimi`, `kimi-k2.6`, `kimi-k2.5`, `kimi-k2` o `kimi-k2-thinking`.
- `KIMI_TIMEOUT`: por defecto reutiliza el timeout OpenAI-compatible, `180` segundos.
- `KIMI_THINKING`: `disabled` por defecto para respuestas operativas breves; el servidor local puede seguir devolviendo `reasoning_content`, pero Charlie nunca lo muestra como respuesta final: solo publica `content`.
- `OPENAI_COMPAT_BASE_URL`: por defecto `http://10.20.20.56:4000/v1`.
- `OPENAI_COMPAT_MODEL`: opcional para forzar otro modelo del mismo servidor local.
- `OPENAI_COMPAT_TIMEOUT`: por defecto `180` segundos.
- `CHARLIE_LLM_MAX_TOKENS`: por defecto `1400` y reutilizado por las funciones auxiliares que no fijan un límite más específico.

Permisos globales: `GET /api/charlie/actions`, MCPs, documentación, inteligencia ambiental y contexto de registro aceptan `charlie.read` o `charlie.execute`; `POST /api/charlie/message`, `POST /api/charlie/transcribe-audio`, `POST /api/charlie/notes` y `POST /api/charlie/execute` requieren `charlie.execute`.

El proveedor auxiliar no ejecuta acciones directamente. Cada consumidor valida su resultado, mantiene la trazabilidad del módulo y aplica sus propias reglas de guardado o confirmación.

MiniMax queda disponible como proveedor alternativo con `CHARLIE_LLM_PROVIDER=minimax`, `MINIMAX_API_KEY` o `data/minimax-api-key.txt`, `MINIMAX_BASE_URL`, `MINIMAX_MODEL` y `MINIMAX_TIMEOUT`.

## Backend Bridge Hermes Charlie

AnimalCharlie puede usar `/api/charlie/message` como fachada compatible hacia Hermes Charlie V2. El navegador mantiene el mismo contrato y nunca recibe tokens ni llama directamente a LiteLLM. No llama a LiteLLM desde el navegador: cualquier salida a Hermes Charlie se hace solo desde `server.py`.

Configuración segura:

- `CHARLIE_V2_ENABLED` o `CHARLIE_IA_V2_ENABLED`: activa el puente. En producción debe permanecer activo para que `/api/charlie/message` use Hermes Charlie.
- `CHARLIE_HERMES_BASE_URL`: URL interna de Hermes Charlie. No se publica al frontend.
- `CHARLIE_HERMES_MESSAGE_PATH`: ruta del endpoint de mensajes en Hermes Charlie; por defecto `/api/charlie/message`.
- `CHARLIE_HERMES_TOOL_SELECTOR_PATH`: ruta del selector estructurado; por defecto `/api/charlie/select-tools`.
- `CHARLIE_HERMES_TOOL_SELECTOR_TIMEOUT`: tiempo máximo de la selección previa, separado de la síntesis final; por defecto 50 segundos para no degradar a reglas locales cuando GPT-5.5 tarda algo más en una consulta válida.
- `CHARLIE_HERMES_TOKEN` o `HERMES_CHARLIE_TOKEN`: token interno para la cabecera `Authorization: Bearer ...`.
- `CHARLIE_HERMES_TOKEN_PATH`: ruta local no versionada para leer el token si no viene por entorno; por defecto `data/charlie-hermes-token.txt`.
- `CHARLIE_HERMES_REQUIRE_TOKEN`: si vale `1`, el puente se considera no configurado hasta encontrar token.
- `CHARLIE_HERMES_FALLBACK_ENABLED`: debe permanecer en `0`; ante fallos solo se admite degradación V2 con datos reales ya cargados y marca `provider.degraded`.
- `CHARLIE_HERMES_TIMEOUT`: timeout de llamada backend a Hermes Charlie.
- `CHARLIE_HERMES_PROVIDER`, `CHARLIE_HERMES_MODEL` y `CHARLIE_HERMES_AGENT`: metadatos seguros para diagnóstico, sin secretos. El modelo no tiene un valor heredado por defecto: si no se declara, la UI indica que lo gestiona Hermes y la respuesta real actualiza el dato cuando el proveedor lo devuelve.
- `CHARLIE_HERMES_MAX_MESSAGE_CHARS`, `CHARLIE_HERMES_MAX_CONTEXT_CHARS` y `CHARLIE_HERMES_MAX_RESULTS`: límites del contrato operativo enviado.
- `BILLING_EXPENSE_LITELLM_MODEL`: modelo GPT del Hermes local para `hermes-module-billing` al leer facturas y tickets; por defecto `gpt-5.5`. `BILLING_EXPENSE_LITELLM_TIMEOUT`, `BILLING_EXPENSE_LITELLM_MAX_TOKENS` y `BILLING_EXPENSE_HERMES_TIMEOUT` acotan esa operación multimodal. No reutiliza el cliente auxiliar Kimi de `server.py`.
- `CHARLIE_CONVERSATION_MAX_HISTORY_MESSAGES`, `CHARLIE_CONVERSATION_MAX_HISTORY_CHARS` y `CHARLIE_CONVERSATION_MAX_MESSAGE_CHARS`: ventana segura de memoria conversacional; por defecto se usan como máximo 12 mensajes anteriores y 16.000 caracteres.

Antes de consultar los datos, AnimalCharlie construye el catálogo permitido desde los manifiestos MCP y llama al selector GPT-5.5 de Hermes. El selector devuelve `candidateId`, intención `read|clarify|write`, como máximo dos lecturas, parámetros, escritura solicitada, campos imprescindibles pendientes y referencias conversacionales. El backend vuelve a validar cada acción, parámetro, permiso y módulo contra el catálogo real; una acción inventada o no autorizada se descarta. El ranking semántico local queda únicamente como degradación cuando el selector no responde, nunca como tabla fija módulo→herramienta de la ruta principal.

El payload de síntesis que AnimalCharlie envía después a Hermes Charlie incluye `requestId`, `conversation.id`, los turnos recientes ordenados, usuario/rol/permisos calculados por AnimalCharlie, módulo activo, etiqueta de vista, ruta, título seguro del registro activo, entidad activa, mensaje, límites, `requestPolicy`, selección validada y contexto local ya sanitizado. El mapa `mcpContext.programMap` conserva todos los módulos de AnimalCharlie con propósito, instrucciones IA y herramientas; `activeModule` y `relevantModules` permiten entender la vista sin abrir otra sesión ni otro endpoint. El runtime construye los mensajes para LiteLLM en el orden `system → historial user/assistant → mensaje actual con contexto`. `allowWrites` se fuerza siempre a `false`: en modo `write`, Hermes puede devolver un único bloque estructurado `charlie-action`, que el piloto valida y retira de la prosa antes de entregar un borrador a AnimalCharlie. Las escrituras siguen pasando por `/api/charlie/execute` con confirmación visible.

Además de los mensajes, `charlie_conversations.memory_json` conserva de forma acotada el último registro, el último conjunto de resultados, la herramienta usada y la escritura pendiente. Esa memoria permite resolver expresiones como `ese cliente`, `la anterior`, `los que faltaban` y `hazlo mañana` sin inventar identificadores: los IDs solo se reutilizan si llegaron desde una lectura segura o desde la ficha activa.

Errores normalizados: `charlie_v2_disabled`, `charlie_v2_not_configured`, `charlie_v2_unauthorized`, `charlie_v2_module_denied`, `charlie_v2_model_unavailable`, `charlie_v2_timeout`, `charlie_v2_tool_denied`, `charlie_v2_invalid_response` y `charlie_v2_upstream_error`.

La interfaz debe presentar estos errores por su `message`, `detail`, `code` o `error` seguro. No debe mostrar objetos serializados como `[object Object]`; si el backend devuelve un error estructurado, el usuario verá el código/mensaje normalizado sin exponer secretos ni payloads internos.

Auditoría: cada intento V2 registra `charlie_v2_message` con `requestId`, módulo, tipo de entidad, duración, proveedor/modelo/agente y resultado. No guarda el prompt completo ni tokens. El evento `charlie_message` asociado conserva compatibilidad y registra longitud del mensaje, runtime usado y borrador propuesto cuando exista.

## Editor IA De Informes

El editor de informes usa Charlie desde la barra `#editorToolbar` mediante el botón `#aiImproveReportBtn`. La UI captura selección, sección actual o informe completo, registra la instrucción en `payload.aiInstructionLog` y llama a `/api/charlie/execute` con la acción `improve_report_text`.

La acción es de lectura y devuelve una propuesta estructurada:

- `improvedText`: texto propuesto.
- `explanation`: motivo breve de la mejora.
- `warnings`: avisos cuando falten datos, evidencias o contexto.
- `provider`: motor usado por Charlie.

El frontend muestra la propuesta en `#reportAiPreview` y solo modifica `#manualPreview` si el usuario pulsa `Aplicar` o `Insertar debajo`. Si el proveedor externo no responde a tiempo, la UI aborta la espera remota y reintenta con `llmMode: local`; el backend limita esa espera con `CHARLIE_REPORT_AI_TIMEOUT` para no bloquear el editor. La auditoría de `improve_report_text` guarda instrucción, alcance, informe y longitud del texto, pero no el cuerpo completo del informe.

Seguimiento funcional: `docs/EDITOR_IA_INFORMES.md`.

## Voz y Audio

Charlie tiene dos entradas de voz:

- Dictado directo en navegador mediante `SpeechRecognition`/`webkitSpeechRecognition`.
- Carga drag and drop de audio con `POST /api/charlie/transcribe-audio`.

La ruta de audio recibe un JSON con `filename`, `contentType`, `dataUrl` en base64 y `language`. El servidor guarda el audio en `data/audio/`, usa Whisper local por defecto, audita el tamaño real recibido y devuelve `transcript`. Ese texto puede corregirse manualmente antes de enviarlo a Charlie o procesarlo como informe.

Opciones:

- `BEA_WHISPER_EXTERNAL_ENABLED`: por defecto `0`; poner `1` para usar el proveedor externo OpenAI-compatible.
- `BEA_WHISPER_TRANSCRIPTION_URL`: opcional; si se activa externo sin URL explícita usa `http://10.20.20.56:4000/v1/audio/transcriptions`.
- `BEA_WHISPER_MODEL`: por defecto `whisper-large-v3-turbo` cuando el externo está activo.
- `BEA_WHISPER_API_KEY`: clave del proveedor externo; configurar solo en el entorno de la máquina o en `data/whisper-api-key.txt`.
- `BEA_WHISPER_TIMEOUT`: por defecto `120` segundos.
- `BEA_WHISPER_EXTERNAL_FALLBACK`: por defecto `1`; si el externo falla, usa Whisper local.
- `WHISPER_BIN`: binario de Whisper, por defecto `whisper`.
- `WHISPER_MODEL`: por defecto `small`.
- `WHISPER_TIMEOUT`: por defecto `360` segundos.
- `WHISPER_UV_PYTHON`: Python usado por el fallback `uv tool run --from openai-whisper whisper`; por defecto `/home/hermes/.local/bin/python3.11`.

## Política de Seguridad

Charlie distingue entre acciones de lectura y escritura.

- Lectura: puede ejecutarse directamente si el rol tiene permisos.
- Escritura: requiere `confirm: true`.
- Borrado destructivo: no se expone como acción de Charlie en esta fase.
- Roles: `admin`, `technician`, `reviewer`; cada acción declara sus roles permitidos.
- Auditoría: se registra usuario, rol, acción, parámetros y resultado principal.

## Acciones Iniciales

| Acción | Módulo | Tipo | Confirmación |
| --- | --- | --- | --- |
| `search` | Global | lectura | No |
| `dashboard_summary` | Dashboard | lectura | No |
| `module_insights` | Global | lectura | No |
| `module_control_plan` | Global | lectura | No |
| `list_module_mcps` | Charlie | lectura | No |
| `module_mcp_context` | Charlie | lectura | No |
| `agent_runbook` | Global | lectura | No |
| `sige_fill_from_attachments` | SIGE | lectura | No |
| `search_medicines` | Vetiquín | lectura | No |
| `recommend_medicines` | Vetiquín | lectura | No |
| `list_clients` | Clientes | lectura | No |
| `list_overdue_invoices` | Facturación | lectura | No |
| `list_today_calendar` | Calendario | lectura | No |
| `staff_contracts_overview` | Personal | lectura | No |
| `list_employees` | Personal | lectura | No |
| `prioritize_work` | Global | lectura | No |
| `client_followup_plan` | Clientes | lectura | No |
| `billing_risk_report` | Facturación | lectura | No |
| `billing_expense_from_document` | Facturación | lectura | No |
| `medicine_review_queue` | Vetiquín | lectura | No |
| `medicine_withdrawal_watchlist` | Vetiquín | lectura | No |
| `milk_quality_review_queue` | Calidad lechera | lectura | No |
| `data_quality_audit` | Global | lectura | No |
| `record_briefing` | Global | lectura | No |
| `improve_report_text` | Certificación | lectura | No |
| `create_client` | Clientes | escritura | Sí |
| `create_action` | Acciones | escritura | Sí |
| `create_note` | Global | escritura | Sí |
| `create_calendar_event` | Calendario | escritura | Sí |
| `create_project` | Proyectos | escritura | Sí |
| `create_billing_invoice_draft` | Facturación | escritura | Sí |
| `create_staff_contract_draft` | Personal | escritura | Sí |

Las funciones nuevas de lectura calculan prioridades, riesgos, huecos, informes CMT, contratos o planes MCP sin escribir datos. Si Charlie propone convertir una recomendación en tarea, nota, cliente, evento, proyecto, factura o contrato, lo hace mediante una acción de escritura separada y confirmada.

En SIGE, `sige_fill_from_attachments` lee los documentos vinculados al expediente activo, usa el proveedor LLM configurado para Charlie cuando está disponible y devuelve sugerencias con campo, valor, confianza, fuente y motivo. No guarda campos del SIGE: la UI muestra cada propuesta, el usuario selecciona cuáles aplicar y después guarda el expediente por el flujo normal.

Las acciones de contratos de Personal conservan el mismo criterio sensible que el módulo: solo aparecen y se ejecutan con rol `admin`. Para otros roles, Charlie puede enseñar métricas generales de Personal, pero no listar ni preparar contratos.

## Ejemplo de Mensaje

```http
POST /api/charlie/message
Authorization: Bearer <token>
Content-Type: application/json

{
  "requestId": "charlie-123",
  "conversationId": "charlie-conversation-123",
  "message": "busca facturas vencidas",
  "context": {
    "module": "billing"
  }
}
```

## Ejemplo con Vetiquín

```http
POST /api/charlie/message
Authorization: Bearer <token>
Content-Type: application/json

{
  "message": "recomienda medicamentos con principio activo amoxicilina para bovino",
  "context": {
    "module": "medicines"
  }
}
```

Charlie responde con resultados de `search_medicines` o `recommend_medicines`, enlazados al módulo `medicines`. En recomendaciones clínicas no debe inventar pauta, vía ni tiempos de espera: solo resume datos CIMAVet y recuerda revisar ficha técnica, especie destino e indicación.

## Ejemplo de Transcripción

```http
POST /api/charlie/transcribe-audio
Authorization: Bearer <token>
Content-Type: application/json

{
  "filename": "visita-explotación.webm",
  "contentType": "audio/webm",
  "language": "es",
  "dataUrl": "data:audio/webm;base64,..."
}
```

## Ejemplo de Ejecución Confirmada

```http
POST /api/charlie/execute
Authorization: Bearer <token>
Content-Type: application/json

{
  "action": "create_client",
  "confirm": true,
  "parameters": {
    "name": "Ganadería Demo S.L.",
    "email": "demo@example.com",
    "phone": "+34 600 000 000"
  }
}
```

La acción confirmada `create_client` aplica las mismas reglas básicas que el
módulo Clientes: valida el email, normaliza el NIF/CIF y rechaza el alta si ya
existe otro cliente no archivado con esa identidad fiscal.

## Integración con un Modelo IA

El navegador no elige herramientas. `server.py` entrega al selector interno `/api/charlie/select-tools` únicamente las candidatas autorizadas y valida su respuesta antes de consultar datos o preparar un borrador. El modelo no puede inventar endpoints ni saltarse `/api/charlie/execute`.

Flujo recomendado:

1. Interpretar la petición y las referencias usando la conversación completa.
2. Elegir la lectura o escritura exacta del catálogo permitido.
3. Validar permisos, parámetros y campos imprescindibles en AnimalCharlie.
4. Ejecutar lecturas sin confirmación o formular una única pregunta mínima.
5. Si la petición escribe, mostrar un solo borrador con registro y campos antes de confirmar.
6. Ejecutar únicamente con `confirm: true` y mostrar el registro resultante.

## Estado Actual

Charlie queda preparado como capa IA transversal del ERP. La interfaz web incluye cálculo ambiental en segundo plano, sugerencias puntuales en módulos concretos, el módulo `Charlie`, el catálogo de acciones, el chat, el panel de confirmación, respuestas LLM en segundo plano y notas IA persistentes por módulo o registro.
