# Calidad Lechera

El módulo `Calidad lechera` convierte lecturas CMT recibidas por WhatsApp, audio o archivo TXT en una tabla estructurada por cuarterones y en un informe editable. La ruta visual es `#calidad-lechera` y el módulo interno es `milk_quality`.

## Flujo Operativo

1. Abrir `Calidad > Calidad lechera`.
2. Completar cliente, explotación, REGA, fecha, técnico y teléfono de aprobación si procede.
3. Abrir `Campos del informe de visita` y completar, si se conocen, los campos del modelo del informe: interlocutor, colegiado, tipo/código de producción, calidad de tanque, índices de ubre, rendimiento de ordeño, máquina, rutina y secado. Este bloque se abre como panel flotante con cierre propio para no desplazar los botones de entrada (`Archivo TXT`, `Audio`, `Últimos WhatsApp`) ni el textarea. Si aún no existen esos datos, quedan en el payload para rellenarse más adelante desde APIs o módulos.
4. Cargar información desde una de estas entradas:
   - Pegar texto de WhatsApp en `#milkQualitySourceText`.
   - Usar `Archivo TXT` para importar texto plano o Markdown.
   - Soltar un archivo sobre la zona `Suelta audio o TXT aquí`.
   - Usar `Audio` y `Transcribir audio` para convertir un `.opus`, `.ogg`, `.mp3`, `.m4a` u otro audio soportado por Whisper.
   - Usar `Últimos WhatsApp` para abrir un panel emergente con los audios recientes de la pasarela local desde `/api/milk-quality/whatsapp-messages`; permite filtrar por fecha o remitente, seleccionar varios audios y enviarlos a Whisper en lote.
5. Confirmar que el audio aparece en la cola visible debajo de los botones. Si no aparece, el archivo no se ha seleccionado.
6. Cada audio individual, TXT o mensaje WhatsApp usado en el módulo sustituye el lote activo. La selección múltiple de `Últimos WhatsApp` concatena las transcripciones seleccionadas y después reconstruye la tabla CMT.
7. El audio empieza a transcribirse automáticamente al importarlo; se envía a un job en segundo plano para evitar que el navegador corte la petición cuando Whisper tarda varios minutos.
8. Mientras Whisper trabaja, la cola muestra si el audio está `en cola` o `Whisper activo` con tiempo transcurrido. El módulo bloquea nuevos audios hasta que termine para no duplicar transcripciones.
9. Pulsar `Pasar a texto estructurado` para regenerar la tabla CMT si se modifica manualmente el texto.
10. Pulsar `Rehacer informe` o `Rehacer informe con el texto actual` si se generó demasiado pronto mientras Whisper seguía transcribiendo; esta acción vuelve a leer todo el textarea actual y reconstruye tabla, hallazgos, resumen de aprobación e informe.
11. Revisar y modificar el informe en el editor tipo Word.
12. Usar `Descargar PDF` para generar un PDF server-side desde `/api/milk-quality/pdf` con los permisos del módulo `milk_quality`.
13. Guardar con `Guardar informe`; queda en `/api/reports` con `payload.schema = "milk-quality-cmt/v1"`.
14. Enviar el resumen de aprobación por WhatsApp si hay teléfono configurado.

## Modelo De Informe De Visita

El módulo ya captura campos del informe completo aunque no salgan de los audios CMT:

| Grupo | Campos |
| --- | --- |
| Visita | Interlocutor, colegiado, explotación, REGA, fecha y técnico |
| Calidad de producción | Tipo de producción, código, bacterias, células, punto crioscópico, grasa, proteína, urea, sólidos e inhibidores |
| Salud de ubre | LS, días en leche y producción |
| Ordeño | Hora de inicio, hora fin, duración, número de animales y rendimiento |
| Máquina | Vacío, pulsación, relación de pulsación, dispositivo de medición y notas técnicas |
| Rutina y secado | Observaciones de rutina, bienestar, animales a secar, tratamientos y recomendaciones |
| Animales revisados | Estado, número de granja, crotal, RCS, NP, días, leche, aporte, aislamiento, CMT por cuarterones y observaciones |

Los audios rellenan principalmente las lecturas CMT y observaciones por vaca. El resto queda estructurado en `payload.meta`, `payload.formData` y en el informe editable para integrarlo después con LIGAL, almacén lechero, clientes/explotaciones, reproductivo, tratamientos o máquina de ordeño.

## Estructura CMT

La lectura se guarda siempre en el orden de cuadrante:

| Campo | Cuarterón |
| --- | --- |
| `DI` | Delantero izquierdo |
| `DD` | Delantero derecho |
| `TI` | Trasero izquierdo |
| `TD` | Trasero derecho |

Escala usada por el módulo:

| Valor | Interpretación operativa |
| --- | --- |
| `0` | Negativo |
| `1` | Trazas o positivo leve |
| `2` | Positivo leve |
| `3` | Positivo, requiere revisión |
| `4` | Mamitis clínica |
| `5` | Cuarterón seco |
| `6` | Sangre |

El parser acepta líneas como:

```text
Vaca 856: 0-0-3-0.
Vaca 842: negativo.
788 1-0-4-0, trasero izquierdo con secreción serosa y atrofia.
Vaca 856 0-0-3-0; 646 0-1-0-0; 738 2-0-3-2; 842 negativo.
BACA 2008-5021 BACA 2012-401.
Vaca 20-44-10-01.
Vaca 1572, 0030.
Vaca 20, 44, 1, 0, 0, 1.
Vaca 15, 0, 2, 2, 4, 0, 0.
Vaca, 1948, 1110.
Vaca 2257. 0500.
```

En esos formatos compactos, el parser reconstruye el número de granja antes de leer los cuatro cuarterones: `20, 44, 1, 0, 0, 1` pasa a vaca `2044` con CMT `1-0-0-1`; `15, 0, 2, 2, 4, 0, 0` pasa a vaca `1502` con CMT `2-4-0-0`.

## Informe Editable

La plantilla de informe se inspira en el formato de visita del ejemplo `docs/entreno/2026-05-25 Informe de ordeño GRILLE_S_C_G.pdf`: portada limpia, cabecera azul, bloques de métricas, tabla de animales revisados, mosaico CMT, animales problema, recomendaciones y firmas.

El informe generado incluye:

- Portada `Informe de visita` con explotación, fecha, técnico, interlocutor y REGA.
- Página de información general de la granja con métricas de calidad, índices de salud de ubre, rendimiento de ordeño y narrativa técnica.
- Tabla `Animales revisados` con estado operativo, número de granja, crotal, RCS, NP, días, leche, aporte, lecturas DI/DD/TI/TD, aislamiento y observaciones.
- Mosaico `CMT` con cada vaca en una tarjeta de cuatro cuarterones y escala 0-6.
- Bloque `Animales problema` para vacas con CMT alto u observaciones.
- Secciones de rutina, máquina de ordeño y secado aunque queden pendientes de alimentar por otras fuentes.
- Recomendaciones operativas no prescriptivas.
- Anexo con el texto fuente o transcripción.
- Bloque de firma.

El contenido se puede editar antes de guardar. `Copiar` extrae el texto visible del editor y `Descargar PDF` envía el editor renderizado a `/api/milk-quality/pdf` con `renderMode: "milk_quality_screen"`, tema y densidad actuales. El backend imprime esa misma vista con Chrome, reintenta si el primer render falla y solo usa el PDF mínimo de emergencia si ambos intentos fallan.

En PDF el informe no se imprime como un documento continuo: la portada, el resumen, los bloques de `Animales revisados`, los bloques `CMT`, los bloques de `Animales problema` y el cierre se renderizan como folios A4 independientes de 210 x 297 mm. Cada folio técnico arranca con cabecera de informe, pie propio y tablas troceadas por bloques para evitar que Chrome corte una tabla larga a mitad de página.

## WhatsApp

El módulo usa endpoints propios para no depender de que el usuario tenga abierto o concedido el módulo genérico `Integraciones`:

- Lectura: `POST /api/milk-quality/whatsapp-messages`, que exige acceso a `milk_quality`, llama internamente a `whatsapp.messages.search` con `direction: "inbound"`, `limit: 80` y `audioOnly: true`, para no perder audios sin texto.
- Recuperación de audio: `POST /api/milk-quality/whatsapp-media`, que exige acceso a `milk_quality`, llama internamente a `whatsapp.messages.media` con `messageId` del mensaje reciente o cacheado; el módulo crea un `File` temporal y reutiliza la cola de `transcribe-audio-jobs`.
- Envío: `whatsapp.messages.send` con `confirmWrite: true`.

La lista de `Últimos WhatsApp` se abre como panel emergente inmediato: primero muestra `Consultando audios recientes...` y después el contador, filtro por fecha, filtro por remitente, casillas de selección múltiple y los botones `Seleccionar visibles`, `Limpiar selección`, `Transcribir seleccionados` y `Transcribir`.

El resumen de aprobación pide al cliente responder `APROBADO` o `REVISION`. La aprobación no cambia todavía el estado del informe de forma automática; se conserva como flujo de confirmación externo.

## Audio y Whisper

La UI usa `POST /api/charlie/transcribe-audio-jobs` y consulta `GET /api/charlie/transcribe-audio-jobs/{id}` hasta que Whisper termina. Estas rutas aceptan usuarios con `charlie.execute` o acceso a `milk_quality`, para que un técnico de Calidad lechera pueda transcribir audios de WhatsApp sin depender del módulo Charlie completo. El endpoint síncrono `POST /api/charlie/transcribe-audio` se conserva para compatibilidad y pruebas directas. El backend intenta:

1. `WHISPER_BIN` o `whisper` local.
2. Fallback `uv tool run --python $WHISPER_UV_PYTHON --from openai-whisper whisper`.
3. Proveedor externo OpenAI-compatible solo si `BEA_WHISPER_EXTERNAL_ENABLED=1` y existe `BEA_WHISPER_API_KEY` o `data/whisper-api-key.txt`; por defecto apunta a `POST http://10.20.20.56:4000/v1/audio/transcriptions` con modelo `whisper-large-v3-turbo`.

La entrada de audio acepta extensiones explícitas además de `audio/*`: `.opus`, `.ogg`, `.oga`, `.m4a`, `.mp3`, `.wav`, `.aac`, `.webm`, `.flac` y `.mp4`. Esto evita que algunos navegadores oculten audios de WhatsApp aunque el tipo MIME venga vacío o como OGG.

Whisper se ejecuta con una cola única de proceso. Si el usuario vuelve a seleccionar o pulsar transcribir durante un trabajo activo, la UI conserva el job actual y evita lanzar otro proceso pesado con el mismo audio.

Si el backend se reinicia mientras el navegador esta consultando un job, la cola en memoria puede perder ese identificador y responder `charlie_audio_transcription_job_not_found`. La UI trata ese caso como recuperable: tolera cortes breves de conexion, reencola automaticamente el mismo audio una vez y continua mostrando el estado de Whisper sin obligar al usuario a volver a subir el archivo.

Variables relevantes:

| Variable | Valor por defecto |
| --- | --- |
| `BEA_WHISPER_EXTERNAL_ENABLED` | `0` |
| `BEA_WHISPER_TRANSCRIPTION_URL` | vacío; si se activa externo sin URL explícita usa `http://10.20.20.56:4000/v1/audio/transcriptions` |
| `BEA_WHISPER_MODEL` | `whisper-large-v3-turbo` |
| `BEA_WHISPER_API_KEY` | vacío; configurar solo en entorno o `data/whisper-api-key.txt` |
| `BEA_WHISPER_TIMEOUT` | `120` |
| `BEA_WHISPER_EXTERNAL_FALLBACK` | `1` |
| `WHISPER_BIN` | `whisper` |
| `WHISPER_MODEL` | `small` |
| `WHISPER_TIMEOUT` | `360` |
| `WHISPER_UV_PYTHON` | `/home/hermes/.local/bin/python3.11` |
| `CHARLIE_AUDIO_TRANSCRIPTION_JOB_TTL_SECONDS` | `1800` |
| `CHARLIE_AUDIO_TRANSCRIPTION_JOB_LIMIT` | `30` |

## Charlie

Charlie ve el módulo como MCP interno `animal-charlie.milk_quality`. Puede leer últimos informes CMT mediante `milk_quality_review_queue`, calcular contexto ambiental y guardar notas confirmadas en `charlie_notes`.

Límites de IA:

- No diagnosticar ni prescribir tratamiento.
- No inventar dosis, tiempos de espera ni resultados no aportados.
- Recomendar revisión veterinaria, contraste con producción/historial y trazabilidad documental cuando haya CMT alto, cuarterones secos, sangre u observaciones clínicas.

- Los endpoints propios de WhatsApp del módulo (`/api/milk-quality/whatsapp-messages` y `/api/milk-quality/whatsapp-media`) se limitan a audios entrantes recientes: el listado filtra `audioOnly` y la descarga rechaza adjuntos no audio o salientes aunque se conozca el `messageId`.
