Documentación en usted, y panel de LIA con el mismo diseño del dashboard

Toda la documentación pasa de voseo a tratamiento de usted, incluidos los
diagramas y los textos de la interfaz. Los prompts de ambos asistentes lo
piden explícitamente, para que las respuestas generadas también lo respeten.

El panel de LIA en la página de documentación adopta el diseño del dashboard
del turnero: mismo botón circular, mismo panel deslizante con encabezado en
degradado y logo LIA, mismos chips de atajo, burbujas y campo de entrada.
Los atajos se arman con los documentos que ese usuario puede ver.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-04 10:57:28 -05:00
co-authored by Claude Opus 5
parent 71c9dfd424
commit fe96e5b60d
20 changed files with 262 additions and 198 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ final class DocIndex
/** Secciones, en orden de aparición. `admin` = solo administradores. */
public const SECCIONES = [
'manual' => ['titulo' => 'Manual de usuario', 'icono' => 'fas fa-book-reader', 'admin' => false,
'resumen' => 'Cómo usar el sistema, paso a paso, según tu rol.'],
'resumen' => 'Cómo usar el sistema, paso a paso, según su rol.'],
'tecnica' => ['titulo' => 'Documentación técnica','icono' => 'fas fa-code', 'admin' => true,
'resumen' => 'Cada módulo por dentro: tablas, endpoints y dependencias.'],
'arquitectura'=> ['titulo' => 'Arquitectura', 'icono' => 'fas fa-sitemap', 'admin' => true,
+4 -3
View File
@@ -46,8 +46,8 @@ if ($gemini->presupuestoAgotado()) {
if ($contexto === '') {
salir([
'ok' => true,
'respuesta' => 'No encontré nada sobre eso en la documentación a la que tenés acceso. '
. 'Probá con otras palabras, o revisá el índice de la izquierda.',
'respuesta' => 'No encontré nada sobre eso en la documentación a la que usted tiene acceso. '
. 'Pruebe con otras palabras, o revise el índice de la izquierda.',
'fuentes' => [],
]);
}
@@ -72,7 +72,8 @@ documentación incluida abajo. Reglas:
- Si la respuesta no está en la documentación, dilo con claridad. No inventes ni supongas.
- La documentación incluida es la que este usuario tiene permitido consultar. No menciones ni
deduzcas la existencia de contenido que no esté aquí.
- Responde en español, directo y práctico. Si es un procedimiento, enumera los pasos.
- Responde en español, tratando SIEMPRE de usted (nunca tú ni vos). Directo y práctico:
si es un procedimiento, enumera los pasos.
- Usa tablas o viñetas cuando ayuden a leer.
- No inventes rutas, nombres de botones ni consultas SQL que no aparezcan en la documentación.
@@ -13,7 +13,7 @@ En `admin_users` conviven dos campos que parecen redundantes y **no lo son**:
> **Hay que mantener las dos sincronizadas.** Cambiar solo `role` deja al usuario con los permisos viejos, porque el acceso real sale de `role_id`. Este error ya ocurrió: un usuario cambió de rol, la interfaz mostraba el rol nuevo y los módulos seguían siendo los anteriores.
Al cambiar el rol de alguien, actuali las dos a la vez:
Al cambiar el rol de alguien, actualice las dos a la vez:
```sql
UPDATE admin_users
@@ -78,7 +78,7 @@ users / conversations el contacto y su hilo
Van en `migrations/`, con nombre `AAAAMMDD_descripcion.sql`. La convención del repositorio es que sean **idempotentes**`IF NOT EXISTS` y guardas en los `UPDATE`/`INSERT` — para poder correrlas más de una vez sin daño.
> Un cambio aplicado directo en producción sin dejar la migración correspondiente hace que un entorno nuevo no lo tenga. Si tocás el esquema, dejá el archivo.
> Un cambio aplicado directo en producción sin dejar la migración correspondiente hace que un entorno nuevo no lo tenga. Si toca el esquema, deje el archivo.
## Inventario completo
@@ -98,7 +98,7 @@ No hay traza de auditoría que permita reconstruirlos.
## Al hacer cambios
- **Cambio de esquema** → dejá la migración en `migrations/`, idempotente.
- **Endpoint nuevo** → verifi permisos ahí adentro, no confíes en la vista.
- **Tocar el turnero** → es lo que más gente usa a diario; probá con un turno real.
- **Cambio de esquema** → deje la migración en `migrations/`, idempotente.
- **Endpoint nuevo** → verifique permisos ahí adentro, no confíes en la vista.
- **Tocar el turnero** → es lo que más gente usa a diario; pruebe con un turno real.
- **Tocar formularios firmados** → tienen valor legal. Un render roto es un documento inválido.
@@ -4,23 +4,23 @@ Lo mínimo para moverse por el sistema, sin importar el rol.
## Entrar
Se ingresa con usuario y contraseña. Si no reconocés tu usuario, buscá tu **número de cédula**: la mayoría de las cuentas del personal se crearon así.
Se ingresa con usuario y contraseña. Si no reconoce su usuario, busque su **número de cédula**: la mayoría de las cuentas del personal se crearon así.
Al entrar vas directo a la pantalla principal de tu rol. No todos ven lo mismo: el menú de la izquierda muestra únicamente los módulos habilitados para vos.
Al entrar va directo a la pantalla principal de su rol. No todos ven lo mismo: el menú de la izquierda muestra únicamente los módulos habilitados para usted.
## Si no ves algo que deberías ver
## Si no ve algo que deberías ver
Casi siempre es una de estas dos:
1. **Te cambiaron los permisos hace poco.** Los permisos se cargan **al iniciar sesión**. Cerrá sesión y volvé a entrar.
2. **Tu rol no lo incluye.** Pedile a un administrador que lo revise.
1. **Le cambiaron los permisos hace poco.** Los permisos se cargan **al iniciar sesión**. Cierre sesión y vuelva a entrar.
2. **Su rol no lo incluye.** Pídale a un administrador que lo revise.
## Cómo está organizado
| Zona | Qué contiene |
|---|---|
| Menú izquierdo | Los módulos a los que tenés acceso |
| Barra superior | Tu usuario y el cierre de sesión |
| Menú izquierdo | Los módulos a los que tiene acceso |
| Barra superior | Su usuario y el cierre de sesión |
| Centro | La pantalla activa |
## Los módulos principales
@@ -59,13 +59,13 @@ Un turno **nunca se borra**. Si el paciente no aparece se marca *ausente*; si no
**Los turnos no se borran.** Se cancelan o se marcan como ausente, pero quedan registrados. Es a propósito: el historial tiene valor clínico y administrativo.
**Las firmas quedan con nombre y cédula.** Cuando firmás un formulario, el sistema registra quién sos. No es opcional ni configurable.
**Las firmas quedan con nombre y cédula.** Cuando firma un formulario, el sistema registra quién es. No es opcional ni configurable.
**Una muestra pendiente no se pierde.** Si un paciente queda debiendo una muestra y vuelve otro día, aparece sola en la estación, marcada como *visita anterior*, con los exámenes de aquella orden.
**Nadie factura lo que no terminó.** En los reportes del día, lo cobrado en turnos finalizados y lo que sigue en curso se muestran por separado. Los ausentes y cancelados no se cuentan.
## Manual según tu rol
## Manual según su rol
- [Recepción](?m=soporte&v=documentacion&s=manual&d=recepcion)
- [Toma de muestras](?m=soporte&v=documentacion&s=manual&d=toma-de-muestras)
@@ -63,8 +63,8 @@ Los videos van completos; a las imágenes se les fija cuántos segundos duran. S
### Si la pantalla se queda pegada
Recargá la página en el televisor. Si el contenido no cambió, verifi que la lista tenga elementos activos en la configuración.
Recargue la página en el televisor. Si el contenido no cambió, verifique que la lista tenga elementos activos en la configuración.
## Verificar paciente
Una consulta rápida por cédula para ver la ficha y el historial de alguien, sin abrir su turno. Útil cuando el paciente pregunta algo en el mostrador y no querés perder lo que estás haciendo.
Una consulta rápida por cédula para ver la ficha y el historial de alguien, sin abrir su turno. Útil cuando el paciente pregunta algo en el mostrador y no quiere perder lo que está haciendo.
+15 -15
View File
@@ -4,19 +4,19 @@ roles: recepcionista, lab_recepcion, supervisor
# Recepción
Guía de la pantalla de recepción del turnero: desde que llamás al paciente hasta que pasa a toma de muestras.
Guía de la pantalla de recepción del turnero: desde que llama al paciente hasta que pasa a toma de muestras.
## Tu escritorio
## Su escritorio
Si el equipo está registrado por IP o token, el menú te muestra **solo tu escritorio**. Si no lo está, ves todos y elegís.
Si el equipo está registrado por IP o token, el menú le muestra **solo su escritorio**. Si no lo está, ve todos y elige.
Esto lo configura un administrador en `Configuración del turnero`. Si estás viendo escritorios que no son el tuyo, avisá: significa que ese equipo no quedó registrado.
Esto lo configura un administrador en `Configuración del turnero`. Si está viendo escritorios que no son el tuyo, avise: significa que ese equipo no quedó registrado.
## El flujo completo
### 1. Llamar al paciente
**Llamar siguiente** toma el turno con mayor prioridad de la cola. También podés llamar a uno específico si hace falta saltarse el orden.
**Llamar siguiente** toma el turno con mayor prioridad de la cola. También puede llamar a uno específico si hace falta saltarse el orden.
El turno aparece en la pantalla de TV de la sala de espera.
@@ -24,22 +24,22 @@ Si el paciente no responde, **Marcar ausente**. Queda registrado como ausente y
### 2. Verificar o crear el paciente
Bus por cédula o nombre.
Busque por cédula o nombre.
- **Existe** → se cargan sus datos y su historial.
- **No existe** → creá la ficha. Cédula, nombre completo, fecha de nacimiento, teléfono y EPS.
- **No existe** → cree la ficha. Cédula, nombre completo, fecha de nacimiento, teléfono y EPS.
> El teléfono importa más de lo que parece: es a donde se envían los consentimientos y las encuestas. Un número mal escrito significa un consentimiento que nunca llega.
### 3. Seleccionar exámenes
Cargá los exámenes solicitados. Si viene con una orden en RIPS, se pueden importar directamente en vez de cargarlos a mano.
Cargue los exámenes solicitados. Si viene con una orden en RIPS, se pueden importar directamente en vez de cargarlos a mano.
Al elegir los exámenes, el sistema decide solo qué consentimientos hacen falta: algunos van atados a un examen concreto (VIH, por ejemplo) y otros a la estación de destino.
### 4. Datos de facturación
Valor cobrado, método de pago, número de recibo. Si es por empresa o EPS, cargá el NIT y la autorización.
Valor cobrado, método de pago, número de recibo. Si es por empresa o EPS, cargue el NIT y la autorización.
Se admite pago combinado — efectivo más tarjeta, por ejemplo.
@@ -63,31 +63,31 @@ Quién firma qué, y dónde:
Los que hagan falta aparecen listados con su estado. Se envían al WhatsApp del paciente, que los firma desde el celular.
**No podés guardar la solicitud si quedan consentimientos sin firmar**, salvo que sea una visita de *solo entrega de muestras*.
**No puede guardar la solicitud si quedan consentimientos sin firmar**, salvo que sea una visita de *solo entrega de muestras*.
Si el envío por WhatsApp falla, el sistema manda un enlace en texto plano como respaldo.
### 6. Elegir destino y guardar
Seleccioná la estación de toma de muestras y guardá. El turno pasa a esa estación y el paciente sale de tu escritorio.
Seleccione la estación de toma de muestras y guarde. El turno pasa a esa estación y el paciente sale de su escritorio.
## Situaciones frecuentes
### El paciente solo viene a entregar una muestra
Mar **Solo entrega de muestras**. Se saltan los consentimientos por examen y no hace falta seleccionar exámenes.
Marque **Solo entrega de muestras**. Se saltan los consentimientos por examen y no hace falta seleccionar exámenes.
> Ojo: el formulario **Datos Toma de Muestras (F-LAB-08)** se sigue exigiendo en la estación. Ese no se omite nunca, porque recoge la historia clínica del momento de la toma.
### El paciente ya vino antes y quedó debiendo una muestra
No tenés que hacer nada especial. En la estación de toma de muestras le va a aparecer sola, marcada como *visita anterior*, junto con los exámenes de aquella orden.
No tiene que hacer nada especial. En la estación de toma de muestras le va a aparecer sola, marcada como *visita anterior*, junto con los exámenes de aquella orden.
### El paciente no recibió el consentimiento
1. Verifi el número de teléfono en su ficha.
1. Verifique el número de teléfono en su ficha.
2. Reenvialo desde la lista de consentimientos.
3. Si sigue sin llegar, avisá a un administrador: puede ser un problema de la plantilla en Meta, que no se arregla desde acá.
3. Si sigue sin llegar, avise a un administrador: puede ser un problema de la plantilla en Meta, que no se arregla desde acá.
### Hay que corregir algo después de guardar
+10 -10
View File
@@ -20,7 +20,7 @@ La línea de WhatsApp del laboratorio la atiende un bot, pero cuando hace falta
┌─────────────┐ sí ┌──────────────────────┐
│ ¿La tomó un ├────────►│ El bot NO interviene │
│ operador? │ │ Respondés vos
│ operador? │ │ Responde usted
└──────┬──────┘ └──────────────────────┘
│ no
@@ -33,11 +33,11 @@ La línea de WhatsApp del laboratorio la atiende un bot, pero cuando hace falta
El bot responde
```
Lo importante: **cuando tomás una conversación, el bot deja de responder ahí**. No hay riesgo de que le conteste encima al paciente mientras vos estás escribiendo.
Lo importante: **cuando toma una conversación, el bot deja de responder ahí**. No hay riesgo de que le conteste encima al paciente mientras usted está escribiendo.
## Atender una conversación
La lista muestra los hilos con mensajes recientes. Al abrir uno ves el historial completo y podés responder.
La lista muestra los hilos con mensajes recientes. Al abrir uno ve el historial completo y puede responder.
Si el paciente ya está registrado, se ve su ficha; si no, se puede crear desde ahí mismo.
@@ -45,25 +45,25 @@ Si el paciente ya está registrado, se ve su ficha; si no, se puede crear desde
Es una regla de WhatsApp, no del sistema:
> Podés escribir libremente durante **24 horas** desde el último mensaje del paciente. Pasado ese plazo, solo se le puede escribir con una **plantilla aprobada**.
> Puede escribir libremente durante **24 horas** desde el último mensaje del paciente. Pasado ese plazo, solo se le puede escribir con una **plantilla aprobada**.
Por eso a veces el sistema no te deja mandar un texto libre y ofrece plantillas. No es una falla: es la restricción de WhatsApp.
Por eso a veces el sistema no le deja mandar un texto libre y ofrece plantillas. No es una falla: es la restricción de WhatsApp.
Las plantillas las crea y aprueba Meta. Si necesitás una nueva para un caso que se repite, pedila a un administrador — el trámite lleva días.
Las plantillas las crea y aprueba Meta. Si necesita una nueva para un caso que se repite, pídala a un administrador — el trámite lleva días.
## Términos y condiciones
Todo contacto nuevo debe aceptar los términos antes de que el bot converse. Responde **ACEPTO** o **NO ACEPTO**.
Se le vuelven a pedir cuando pasan 6 meses o cuando se publica una versión nueva. Si un paciente dice que no puede abrir el documento de términos, avisá: puede ser que el enlace esté caído, y eso lo resuelve un administrador.
Se le vuelven a pedir cuando pasan 6 meses o cuando se publica una versión nueva. Si un paciente dice que no puede abrir el documento de términos, avise: puede ser que el enlace esté caído, y eso lo resuelve un administrador.
## Problemas frecuentes
**El paciente dice que escribió y nadie le respondió.**
Revisá si la conversación quedó tomada por un operador que no siguió. En ese estado el bot no responde y queda esperando a una persona.
Revise si la conversación quedó tomada por un operador que no siguió. En ese estado el bot no responde y queda esperando a una persona.
**No me deja enviar un mensaje.**
Pasaron más de 24 horas desde el último mensaje del paciente. Usá una plantilla.
Pasaron más de 24 horas desde el último mensaje del paciente. Use una plantilla.
**El paciente no recibe los consentimientos.**
Verifi el número en su ficha. Si está bien y aun así no llegan, es problema de plantilla — avisá a un administrador.
Verifique el número en su ficha. Si está bien y aun así no llegan, es problema de plantilla — avise a un administrador.
@@ -6,13 +6,13 @@ roles: bacteriologo, supervisor
Guía de la estación de toma de muestras: atender al paciente, recibir sus muestras y firmar los formularios.
## Tu estación
## Su estación
Igual que en recepción, si el equipo está registrado por IP o token, ves **solo tu estación**. Si no, las ves todas.
Igual que en recepción, si el equipo está registrado por IP o token, ve **solo su estación**. Si no, las ve todas.
## Atender un turno
Los pacientes derivados desde recepción aparecen en tu bandeja. Al abrir uno ves su ficha completa: datos, exámenes solicitados, muestras a recibir y formularios pendientes.
Los pacientes derivados desde recepción aparecen en su bandeja. Al abrir uno ve su ficha completa: datos, exámenes solicitados, muestras a recibir y formularios pendientes.
## Recibir muestras
@@ -41,9 +41,9 @@ Al rechazar hay que indicar el motivo. Ese motivo queda registrado y se ve despu
quedan enlazados
```
Si el paciente quedó debiendo una muestra otro día, te aparece con una etiqueta ámbar **visita anterior**, e incluye los exámenes de aquella orden para que sepas de qué se trataba.
Si el paciente quedó debiendo una muestra otro día, le aparece con una etiqueta ámbar **visita anterior**, e incluye los exámenes de aquella orden para que sepa de qué se trataba.
Se reciben con un clic, igual que cualquier otra. Al hacerlo, los dos turnos quedan enlazados: desde el historial podés saltar de uno al otro.
Se reciben con un clic, igual que cualquier otra. Al hacerlo, los dos turnos quedan enlazados: desde el historial puede saltar de uno al otro.
> **El turno original no se modifica.** Sigue finalizado como estaba. Solo se registra en qué visita se completó la muestra.
@@ -53,9 +53,9 @@ Se reciben con un clic, igual que cualquier otra. Al hacerlo, los dos turnos que
Obligatorio en todas las estaciones. Recoge la historia clínica del momento: síntomas, antecedentes familiares y personales, medicación, datos obstétricos si corresponde.
Lo firmás vos, no el paciente.
Lo firma usted, no el paciente.
**Si el paciente ya lo llenó en una visita anterior**, aparece el botón *Cargar datos de la visita anterior*. Trae las respuestas de la última vez para que solo revises y ajustes lo que cambió. **No trae la firma**: esa la ponés vos, con la fecha de hoy.
**Si el paciente ya lo llenó en una visita anterior**, aparece el botón *Cargar datos de la visita anterior*. Trae las respuestas de la última vez para que solo revises y ajustes lo que cambió. **No trae la firma**: esa la pone usted, con la fecha de hoy.
### Control de Tomas de Muestras Prolongadas (F-LAB-28)
@@ -79,10 +79,10 @@ Para exámenes que requieren varias tomas en el tiempo: curvas de glicemia, prol
Cómo funciona:
1. **Mar el examen.** El formulario muestra solo las tomas de ese examen; si el paciente tiene dos exámenes seriados, muestra las de ambos.
2. **Configurá los tiempos** si te lo pide (minuto 0, 30, 60…).
3. **Firmá cada toma** a medida que la hacés. El sistema registra la hora y **quién firmó**.
4. Cuando firmás una, el sistema calcula cuándo toca la siguiente y muestra una cuenta regresiva.
1. **Marque el examen.** El formulario muestra solo las tomas de ese examen; si el paciente tiene dos exámenes seriados, muestra las de ambos.
2. **Configure los tiempos** si se lo pide (minuto 0, 30, 60…).
3. **Firme cada toma** a medida que la hace. El sistema registra la hora y **quién firmó**.
4. Cuando firma una, el sistema calcula cuándo toca la siguiente y muestra una cuenta regresiva.
> Cada toma se firma por separado y queda con el nombre de quien la hizo. Si cambia el turno del personal a mitad del protocolo, cada toma conserva el nombre correcto.
@@ -90,11 +90,11 @@ Si hay que cerrar el protocolo antes de terminar todas las tomas, se puede hacer
## Antes de finalizar
El sistema no te deja finalizar si quedan muestras sin decidir. Cada una tiene que estar recibida, pendiente o rechazada.
El sistema no le deja finalizar si quedan muestras sin decidir. Cada una tiene que estar recibida, pendiente o rechazada.
## Comentarios
Podés dejar notas en el turno. Quedan visibles para el resto del personal y en el historial.
Puede dejar notas en el turno. Quedan visibles para el resto del personal y en el historial.
## Preguntas frecuentes
@@ -102,7 +102,7 @@ Podés dejar notas en el turno. Quedan visibles para el resto del personal y en
Sí. Con el botón de deshacer vuelve a pendiente.
**El formulario me muestra secciones de exámenes que el paciente no tiene.**
Avisá a soporte. Debería mostrar únicamente las del examen marcado.
Avise a soporte. Debería mostrar únicamente las del examen marcado.
**¿Qué pasa si el paciente se va sin dar una muestra?**
Dejala en **pendiente**. Cuando vuelva —el día que sea— le va a aparecer sola a quien lo atienda.
Déjela en **pendiente**. Cuando vuelva —el día que sea— le va a aparecer sola a quien lo atienda.
+14 -14
View File
@@ -4,11 +4,11 @@ roles: enfermero, supervisor
# Enfermeros — domicilios
Guía del portal del enfermero: tus visitas domiciliarias, cómo agendarlas y qué hacer en cada una.
Guía del portal del enfermero: sus visitas domiciliarias, cómo agendarlas y qué hacer en cada una.
## Tu portal
## Su portal
Al entrar vas directo al portal. Ves **tus** domicilios: los que te asignaron y los que agendaste vos.
Al entrar va directo al portal. Ve **sus** domicilios: los que le asignaron y los que agendó usted.
Está pensado para usarse desde el celular en la calle.
@@ -16,8 +16,8 @@ Está pensado para usarse desde el celular en la calle.
**Nuevo domicilio** abre el formulario:
1. **Paciente** — buscalo por cédula. Si no existe, se crea ahí mismo.
2. **Dirección** — la del paciente con un botón, o escribí otra. Agregá indicaciones si el lugar es difícil de encontrar («apto 302, tocar campanilla»).
1. **Paciente** — búsquelo por cédula. Si no existe, se crea ahí mismo.
2. **Dirección** — la del paciente con un botón, o escriba otra. Agregue indicaciones si el lugar es difícil de encontrar («apto 302, tocar campanilla»).
3. **Fecha y hora**.
4. **Servicio** — qué se va a hacer.
5. **Seguro y autorización** si aplica.
@@ -25,15 +25,15 @@ Está pensado para usarse desde el celular en la calle.
## Editar
Podés editar los domicilios **que agendaste vos** y también **los que te asignaron**. El botón *Editar* aparece mientras el domicilio no esté completado ni cancelado.
Puede editar los domicilios **que agendó usted** y también **los que le asignaron**. El botón *Editar* aparece mientras el domicilio no esté completado ni cancelado.
## Avisar al paciente por WhatsApp
Hay un enlace que abre WhatsApp con el mensaje ya escrito, presentándote como profesional del Laboratorio Ximena Caicedo. Solo revisás y enviás.
Hay un enlace que abre WhatsApp con el mensaje ya escrito, presentándote como profesional del Laboratorio Ximena Caicedo. Solo revisa y envía.
## Notas y archivos
Podés dejar dos tipos de nota en cada domicilio:
Puede dejar dos tipos de nota en cada domicilio:
- **Nota de ficha** — estructurada, para datos clínicos.
- **Nota libre** — texto suelto.
@@ -42,29 +42,29 @@ Ambas admiten fotos y archivos adjuntos, útil para órdenes médicas en papel o
## Formularios
Podés enviarle un formulario al paciente para que lo firme desde su celular, o copiar el enlace para pasárselo por otro medio. Los que ya firmó se pueden consultar desde el mismo domicilio.
Puede enviarle un formulario al paciente para que lo firme desde su celular, o copiar el enlace para pasárselo por otro medio. Los que ya firmó se pueden consultar desde el mismo domicilio.
Cuando firmás vos un formulario, queda registrado con tu **nombre y cédula**, tomados de tu ficha de enfermero.
Cuando firma usted un formulario, queda registrado con su **nombre y cédula**, tomados de su ficha de enfermero.
## Estados de un domicilio
| Estado | Significado |
|---|---|
| Programado | Agendado, sin atender |
| En curso | Estás en la visita |
| En curso | Está en la visita |
| Completado | Terminado |
| Cancelado | No se hizo |
## Preguntas frecuentes
**No puedo editar un domicilio.**
Solo se pueden editar los propios o los asignados a vos, y solo si no está completado ni cancelado.
Solo se pueden editar los propios o los asignados a usted, y solo si no está completado ni cancelado.
**El paciente cambió de dirección.**
Editá el domicilio. Si el cambio es permanente, actuali también la ficha del paciente.
Edite el domicilio. Si el cambio es permanente, actualice también la ficha del paciente.
**Necesito reprogramar.**
Editá la fecha y hora, y avisale al paciente por WhatsApp.
Edite la fecha y hora, y avísele al paciente por WhatsApp.
**¿Puedo ver domicilios de otro enfermero?**
No. El portal muestra únicamente los tuyos.
@@ -12,7 +12,7 @@ Tareas de administrador: usuarios, permisos, configuración y reportes.
La convención de la casa es usar el **número de cédula como nombre de usuario** para el personal asistencial.
Cargá también la **cédula** en su ficha: es lo que aparece bajo la firma en los formularios. Si falta, el documento sale firmado sin identificación.
Cargue también la **cédula** en su ficha: es lo que aparece bajo la firma en los formularios. Si falta, el documento sale firmado sin identificación.
Los enfermeros son un caso aparte: su cédula sale de la ficha de enfermero, no del usuario.
@@ -20,7 +20,7 @@ Los enfermeros son un caso aparte: su cédula sale de la ficha de enfermero, no
> Un usuario tiene **dos** campos de rol y hay que cambiar los dos. El texto (`role`) es lo que muestra la interfaz; el vínculo (`role_id`) es de donde salen los permisos reales.
Si cambiás solo uno, el usuario ve un rol y tiene los permisos del otro. Ya pasó.
Si cambia solo uno, el usuario ve un rol y tiene los permisos del otro. Ya pasó.
Después del cambio, **el usuario debe cerrar sesión y volver a entrar**: los permisos se cargan al iniciar sesión, no en cada pantalla.
+7 -7
View File
@@ -22,7 +22,7 @@ SELECT module_slug, permission FROM role_modules WHERE role_id = <role_id>;
Si los datos están bien → **que cierre sesión y vuelva a entrar**. Los permisos se cargan al iniciar sesión, no en cada petición.
Si `role` y `role_id` no coinciden, actuali las dos:
Si `role` y `role_id` no coinciden, actualice las dos:
```sql
UPDATE admin_users
@@ -37,13 +37,13 @@ UPDATE admin_users
## «Un enlace que enviamos por WhatsApp está roto»
Primero, comprobá si el destino responde:
Primero, compruebe si el destino responde:
```bash
curl -s -o /dev/null -w "%{http_code}\n" "<la URL>"
```
**Si devuelve 503 o no resuelve**, el dominio está caído o cambió. Revisá si el archivo existe en el dominio actual del sistema.
**Si devuelve 503 o no resuelve**, el dominio está caído o cambió. Revise si el archivo existe en el dominio actual del sistema.
Las URL que enviamos viven en lugares distintos según el caso:
@@ -71,7 +71,7 @@ UPDATE system_config
## «El mensaje salió por el número equivocado»
Las dos líneas comparten cuenta y token; lo único que cambia es el `phone_number_id`. Revisá que el envío haya especificado el canal:
Las dos líneas comparten cuenta y token; lo único que cambia es el `phone_number_id`. Revise que el envío haya especificado el canal:
```php
$wa = new WhatsAppService('turnero'); // no new WhatsAppService()
@@ -103,8 +103,8 @@ UPDATE lab_config SET valor = '0' WHERE clave = 'lia_tokens_usados';
El formulario F-LAB-28 tiene secciones para todos los exámenes posibles y muestra solo las del examen del paciente. Si aparecen de más:
1. Verifi que el documento se abra con `&embed=1&compact=1` — sin esos parámetros no se aplica el filtrado.
2. Revisá `_tomas_config` dentro de `datos_respuestas`: ahí queda qué ciclos se configuraron.
1. Verifique que el documento se abra con `&embed=1&compact=1` — sin esos parámetros no se aplica el filtrado.
2. Revise `_tomas_config` dentro de `datos_respuestas`: ahí queda qué ciclos se configuraron.
---
@@ -112,7 +112,7 @@ El formulario F-LAB-28 tiene secciones para todos los exámenes posibles y muest
Las firmas de tomas prolongadas registran el profesional **desde el 3 de agosto de 2026**. Los documentos firmados antes no tienen ese dato y **no es recuperable** — no quedó traza en ninguna tabla de auditoría.
Para los nuevos, el nombre y la cédula se resuelven en el servidor desde la sesión de quien firma. Si aparece vacío en un documento reciente, comprobá que el usuario tenga cédula:
Para los nuevos, el nombre y la cédula se resuelven en el servidor desde la sesión de quien firma. Si aparece vacío en un documento reciente, compruebe que el usuario tenga cédula:
```sql
SELECT id, username, full_name, cedula FROM admin_users WHERE id = <id>;
@@ -85,4 +85,4 @@ En **dos** lugares que hay que mantener sincronizados:
Van en `migrations/`, con nombre `AAAAMMDD_descripcion.sql` e idempotentes (`IF NOT EXISTS`, guardas en `UPDATE`/`INSERT`).
Si aplicás un cambio directo en producción, **dejá también la migración**: sin ella, un entorno nuevo no tendrá ese cambio y nadie se va a enterar hasta que falle.
Si aplica un cambio directo en producción, **deje también la migración**: sin ella, un entorno nuevo no tendrá ese cambio y nadie se va a enterar hasta que falle.
@@ -37,9 +37,9 @@ UPDATE admin_users SET cedula = username
MariaDB 11.8 admite `IF NOT EXISTS` en `ALTER TABLE`. Para `UPDATE` e `INSERT`, la guarda va en el `WHERE`.
Antes de dar por buena una migración, corrila dos veces y verifi que la segunda no cambie nada.
Antes de dar por buena una migración, córrala dos veces y verifique que la segunda no cambie nada.
> Aplicar un cambio directo en producción sin dejar la migración hace que un entorno nuevo no lo tenga, y nadie se entera hasta que algo falla. Si tocás el esquema, dejá el archivo.
> Aplicar un cambio directo en producción sin dejar la migración hace que un entorno nuevo no lo tenga, y nadie se entera hasta que algo falla. Si toca el esquema, deje el archivo.
## Archivos subidos
@@ -60,7 +60,7 @@ php -l archivo.php
curl -s -o /dev/null -w "%{http_code}\n" "https://<dominio>/<ruta>"
```
Si el cambio afectó permisos, recordá que **las sesiones abiertas conservan los permisos viejos** hasta que el usuario vuelva a entrar.
Si el cambio afectó permisos, recuerde que **las sesiones abiertas conservan los permisos viejos** hasta que el usuario vuelva a entrar.
## Configuración por entorno
@@ -55,7 +55,7 @@ Es exactamente lo que pasó con el documento de términos cuando cambió el domi
## Antes de un cambio riesgoso
Si vas a tocar datos en producción, respaldá **solo lo que vas a tocar**:
Si va a tocar datos en producción, respalde **solo lo que va a tocar**:
```bash
mysqldump -h <host> -u <usuario> -p <base> role_modules admin_users \
@@ -70,7 +70,7 @@ Un respaldo que nunca se restauró no es un respaldo, es un archivo:
```bash
gunzip -t respaldo_2026-08-03.sql.gz # ¿está íntegro?
zcat respaldo_2026-08-03.sql.gz | head -40 # ¿tiene lo que esperás?
zcat respaldo_2026-08-03.sql.gz | head -40 # ¿tiene lo que espera?
```
Lo ideal es restaurarlo de vez en cuando en una base de prueba y comprobar que el sistema arranca contra ella.
+2 -2
View File
@@ -59,9 +59,9 @@ Ver [Roles y permisos](?m=soporte&v=documentacion&s=arquitectura&d=roles-y-permi
Lo esencial al crear o editar un usuario:
- **`role` y `role_id` deben cambiarse juntos.** La interfaz lee uno, los permisos salen del otro.
- **Cargá la cédula.** Es lo que aparece bajo la firma en los formularios.
- **Cargue la cédula.** Es lo que aparece bajo la firma en los formularios.
- El usuario debe **volver a iniciar sesión** para que un cambio de permisos surta efecto.
## Al agregar un catálogo nuevo
Si es un ABM simple, seguí el patrón de `medicos`: un `views/index.php` y tres endpoints (`list`, `save`, `delete`). Registralo en `SYSTEM_MODULES` y concedelo a los roles que corresponda, o nadie lo verá.
Si es un ABM simple, siga el patrón de `medicos`: un `views/index.php` y tres endpoints (`list`, `save`, `delete`). Registralo en `SYSTEM_MODULES` y concédalo a los roles que corresponda, o nadie lo verá.
+3 -3
View File
@@ -27,10 +27,10 @@ Cada módulo tiene su `api/_helpers.php` con lo común. Los archivos que empieza
### Reglas al agregar uno
1. **Verifi permisos en el propio endpoint.** Que la vista haya ocultado el botón no protege nada.
2. **Resolvé la identidad en el servidor.** Para saber quién hace una acción, usá `adminId()`, no un valor que mande el navegador.
1. **Verifique permisos en el propio endpoint.** Que la vista haya ocultado el botón no protege nada.
2. **Resuelva la identidad en el servidor.** Para saber quién hace una acción, use `adminId()`, no un valor que mande el navegador.
3. **Consultas preparadas siempre.**
4. **Dejá un comentario de cabecera** describiendo qué hace y qué recibe: es lo que aparece en la tabla de abajo.
4. **Deje un comentario de cabecera** describiendo qué hace y qué recibe: es lo que aparece en la tabla de abajo.
## Inventario
+166 -103
View File
@@ -135,47 +135,85 @@ Layout::open('Soporte · Documentación', 'fas fa-life-ring');
.doc-body { padding:20px 18px 60px; }
}
@media print {
.doc-nav, .doc-toc, .doc-ruta, .lia-fab, .lia-panel { display:none !important; }
.doc-nav, .doc-toc, .doc-ruta, #btnAI, #aiPanel { display:none !important; }
.doc-body { max-width:none; padding:0; }
.doc-body pre { background:#f8fafc; color:#0f172a; border:1px solid #cbd5e1; }
}
/* ── Asistente LIA ── */
.lia-fab { position:fixed; right:22px; bottom:22px; z-index:1040; width:54px; height:54px;
border-radius:50%; border:none; cursor:pointer; color:#fff; font-size:1.25rem;
background:linear-gradient(135deg,#2563eb,#7c3aed); box-shadow:0 8px 24px rgba(37,99,235,.4); }
.lia-fab:hover { transform:scale(1.06); }
.lia-panel { position:fixed; right:22px; bottom:88px; z-index:1041; width:390px; max-width:calc(100vw - 44px);
height:520px; max-height:calc(100vh - 130px); background:#fff; border:1px solid #e2e8f0;
border-radius:16px; box-shadow:0 20px 60px rgba(0,0,0,.18); display:none;
flex-direction:column; overflow:hidden; }
.lia-panel.abierto { display:flex; }
.lia-head { padding:12px 15px; background:linear-gradient(135deg,#2563eb,#7c3aed); color:#fff;
display:flex; align-items:center; gap:9px; }
.lia-head .t { font-weight:700; font-size:.92rem; flex:1; }
.lia-head .s { font-size:.7rem; opacity:.85; display:block; font-weight:400; }
.lia-head button { background:none; border:none; color:#fff; cursor:pointer; font-size:1rem; opacity:.85; }
.lia-msgs { flex:1; overflow-y:auto; padding:14px; background:#f8fafc; }
.lia-msg { max-width:88%; padding:9px 12px; border-radius:12px; font-size:.84rem; line-height:1.55;
margin-bottom:9px; word-wrap:break-word; }
.lia-msg.user { background:#2563eb; color:#fff; margin-left:auto; border-bottom-right-radius:3px; }
.lia-msg.bot { background:#fff; color:#1e293b; border:1px solid #e2e8f0; border-bottom-left-radius:3px; }
.lia-msg.bot table { width:100%; border-collapse:collapse; font-size:.76rem; margin:6px 0; }
.lia-msg.bot th, .lia-msg.bot td { border:1px solid #e2e8f0; padding:4px 6px; text-align:left; }
.lia-msg.bot code { background:#f1f5f9; color:#be185d; padding:1px 4px; border-radius:3px; font-size:.9em; }
.lia-msg.bot ul, .lia-msg.bot ol { padding-left:18px; margin:6px 0; }
.lia-fuentes { font-size:.68rem; color:#64748b; margin:-4px 0 10px 2px; }
.lia-fuentes a { color:#2563eb; text-decoration:none; }
.lia-in { display:flex; gap:7px; padding:11px; border-top:1px solid #e2e8f0; background:#fff; }
.lia-in input { flex:1; border:1px solid #cbd5e1; border-radius:9px; padding:8px 11px; font-size:.84rem; }
.lia-in input:focus { outline:none; border-color:#2563eb; }
.lia-in button { border:none; background:#2563eb; color:#fff; border-radius:9px; width:38px; cursor:pointer; }
.lia-in button:disabled { opacity:.5; cursor:default; }
.lia-sug { padding:0 14px 12px; }
.lia-sug button { display:block; width:100%; text-align:left; background:#fff; border:1px solid #e2e8f0;
border-radius:9px; padding:7px 11px; font-size:.79rem; color:#334155; cursor:pointer;
margin-bottom:6px; }
.lia-sug button:hover { border-color:#2563eb; color:#1d4ed8; }
/* ── Panel LIA — mismo diseño que el dashboard del turnero ── */
#aiPanel {
position:fixed; bottom:0; right:24px; width:400px; z-index:1080;
box-shadow:0 -8px 40px rgba(0,0,0,.22); border-radius:20px 20px 0 0;
background:#fff; border:1px solid #e2e8f0; border-bottom:none;
transform:translateY(100%); transition:transform .35s cubic-bezier(.4,0,.2,1);
display:flex; flex-direction:column; max-height:580px;
}
#aiPanel.open { transform:translateY(0); }
#aiPanel.minimized .ai-body { display:none; }
#aiPanel.minimized { max-height:none; }
.ai-header {
background:linear-gradient(135deg,#0d47a1,#1565c0,#0288d1);
color:#fff; padding:14px 18px; border-radius:20px 20px 0 0;
display:flex; align-items:center; gap:12px; flex-shrink:0; cursor:pointer; user-select:none;
}
.lia-logo {
width:44px; height:44px; border-radius:50%;
background:rgba(255,255,255,.15); border:2px solid rgba(255,255,255,.4);
display:flex; align-items:center; justify-content:center;
font-size:1rem; font-weight:900; letter-spacing:-1px; flex-shrink:0;
transition:transform .3s;
}
.lia-logo:hover { transform:scale(1.08); }
.lia-logo .lia-i { color:#7dd3fc; }
.ai-title { font-weight:800; font-size:1.05rem; letter-spacing:.5px; }
.btn-tts { background:rgba(255,255,255,.15); border:1px solid rgba(255,255,255,.3);
color:rgba(255,255,255,.7); cursor:pointer; padding:5px 9px; border-radius:8px;
font-size:.88rem; transition:all .2s; }
.btn-tts:hover { background:rgba(255,255,255,.25); color:#fff; }
.ai-header .btn-close { filter:invert(1) brightness(2); opacity:.7; }
.ai-header .btn-close:hover { opacity:1; }
.ai-body { display:flex; flex-direction:column; flex:1; overflow:hidden; }
.ai-quick { display:flex; gap:7px; flex-wrap:wrap; padding:10px 14px;
border-bottom:1px solid #f1f5f9; flex-shrink:0; background:#fafbff; }
.ai-chip { background:#fff; border:1.5px solid #e2e8f0; border-radius:99px; padding:6px 13px;
font-size:.82rem; cursor:pointer; color:#334155; white-space:nowrap;
transition:all .15s; font-weight:500; }
.ai-chip:hover { background:#eff6ff; border-color:#93c5fd; color:#1d4ed8; }
.ai-messages { flex:1; overflow-y:auto; padding:14px 16px; display:flex; flex-direction:column; gap:10px; }
.ai-msg { max-width:92%; padding:10px 14px; border-radius:16px; font-size:.9rem;
line-height:1.55; animation:msg-in .2s ease; word-wrap:break-word; }
@keyframes msg-in { from{opacity:0;transform:translateY(6px)} to{opacity:1;transform:none} }
.ai-msg.user { background:linear-gradient(135deg,#1565c0,#0288d1); color:#fff;
align-self:flex-end; border-radius:16px 16px 2px 16px; }
.ai-msg.bot { background:#f1f5f9; color:#1e293b; align-self:flex-start; border-radius:16px 16px 16px 2px; }
.ai-msg.bot.typing::after { content:'●●●'; animation:dots 1.2s infinite; letter-spacing:2px; }
@keyframes dots { 0%,100%{opacity:.3} 50%{opacity:1} }
.ai-msg.bot strong { color:#1565c0; }
.ai-msg.bot ul, .ai-msg.bot ol { margin:4px 0 4px 16px; padding:0; }
.ai-msg.bot code { background:#e2e8f0; color:#be185d; padding:1px 4px; border-radius:3px; font-size:.9em; }
.ai-msg.bot pre { background:#0f172a; color:#e2e8f0; padding:9px 11px; border-radius:8px;
overflow-x:auto; font-size:.76rem; margin:6px 0; }
.ai-fuentes { font-size:.68rem; color:#94a3b8; align-self:flex-start; margin:-6px 0 0 4px; }
.ai-footer { padding:10px 14px; border-top:1px solid #f1f5f9; display:flex; gap:8px;
flex-shrink:0; background:#fafbff; }
.ai-footer input { flex:1; border:1.5px solid #e2e8f0; border-radius:10px; padding:9px 14px;
font-size:.9rem; outline:none; background:#fff; }
.ai-footer input:focus { border-color:#1565c0; }
.ai-footer .btn-send { background:linear-gradient(135deg,#1565c0,#0288d1); color:#fff; border:none;
border-radius:10px; padding:9px 16px; cursor:pointer; font-size:.88rem; min-width:42px; }
.ai-footer .btn-send:disabled { opacity:.5; cursor:default; }
#btnAI {
position:fixed; bottom:24px; right:24px; z-index:1079;
background:linear-gradient(135deg,#0d47a1,#1565c0,#0288d1);
color:#fff; border:none; border-radius:50%; width:58px; height:58px;
font-size:.78rem; font-weight:900; letter-spacing:-.5px;
cursor:pointer; box-shadow:0 4px 20px rgba(21,101,192,.45);
transition:transform .2s,box-shadow .2s;
display:flex; align-items:center; justify-content:center;
}
#btnAI:hover { transform:scale(1.1); box-shadow:0 6px 28px rgba(21,101,192,.55); }
#btnAI.hidden { display:none; }
@media(max-width:600px) { #aiPanel { width:100%; right:0; border-radius:16px 16px 0 0; } }
</style>
<div class="doc-wrap">
@@ -269,23 +307,30 @@ Layout::open('Soporte · Documentación', 'fas fa-life-ring');
</div>
</div>
<!-- ── Asistente LIA sobre la documentación ── -->
<button class="lia-fab" id="liaFab" title="Preguntar a LIA"><i class="fas fa-robot"></i></button>
<div class="lia-panel" id="liaPanel">
<div class="lia-head">
<i class="fas fa-robot"></i>
<div class="t">LIA
<span class="s">Responde sobre la documentación que podés ver</span>
<!-- ── Panel LIA — asistente sobre la documentación ── -->
<button id="btnAI" onclick="toggleAI()" title="Preguntar a LIA">LIA</button>
<div id="aiPanel">
<div class="ai-header" onclick="minimizarIA(event)">
<div class="lia-logo" id="liaLogo">
<span>L</span><span class="lia-i">I</span><span>A</span>
</div>
<button onclick="liaToggle()" title="Cerrar"><i class="fas fa-times"></i></button>
<div style="flex:1;line-height:1.1">
<div class="ai-title">LIA</div>
<div style="font-size:.68rem;opacity:.75;font-weight:400">Asistente de Documentación</div>
</div>
<button class="btn-tts" onclick="event.stopPropagation();minimizarIA(event)" title="Minimizar" id="btnMin">
<i class="fas fa-minus" id="iconMin"></i>
</button>
<button class="btn-close" onclick="event.stopPropagation();toggleAI()"></button>
</div>
<div class="lia-msgs" id="liaMsgs">
<div class="lia-msg bot">Hola. Preguntame cómo hacer algo en el sistema y te respondo con la documentación.</div>
<div class="lia-sug" id="liaSug"></div>
</div>
<div class="lia-in">
<input type="text" id="liaInput" placeholder="¿Cómo…?" autocomplete="off">
<button id="liaSend" onclick="liaEnviar()"><i class="fas fa-paper-plane"></i></button>
<div class="ai-body" id="aiBody">
<div class="ai-quick" id="aiQuick"></div>
<div class="ai-messages" id="aiMessages"></div>
<div class="ai-footer">
<input type="text" id="aiInput" placeholder="Pregunta cómo hacer algo…" maxlength="400"
onkeydown="if(event.key==='Enter')enviarIA()">
<button class="btn-send" onclick="enviarIA()"><i class="fas fa-paper-plane"></i></button>
</div>
</div>
</div>
@@ -338,36 +383,60 @@ function esc(s) {
return d.innerHTML;
}
/* ── Asistente LIA sobre la documentación ── */
const LIA_API = '<?= BASE_URL ?>modules/soporte/api/ai_docs.php';
let _liaHist = [];
/* ── LIA: asistente sobre la documentación ──
Mismo comportamiento y nombres que el panel del dashboard del turnero. */
const AI_API = '<?= BASE_URL ?>modules/soporte/api/ai_docs.php';
let _liaHistorial = [];
function liaToggle() {
const p = document.getElementById('liaPanel');
p.classList.toggle('abierto');
if (p.classList.contains('abierto')) document.getElementById('liaInput').focus();
function toggleAI() {
const panel = document.getElementById('aiPanel');
const fab = document.getElementById('btnAI');
const abre = !panel.classList.contains('open');
panel.classList.toggle('open', abre);
panel.classList.remove('minimized');
fab.classList.toggle('hidden', abre);
if (abre) {
if (!document.getElementById('aiMessages').children.length) saludarIA();
document.getElementById('aiInput').focus();
}
}
document.getElementById('liaFab').addEventListener('click', liaToggle);
// Sugerencias tomadas de los documentos que este usuario realmente puede ver
(function () {
const sug = document.getElementById('liaSug');
if (!DOCS.length) return;
sug.innerHTML = DOCS.slice(0, 3)
.map(d => `<button onclick="liaPreguntar('¿Qué explica ${esc(d.t)}?')">${esc(d.t)}</button>`)
function minimizarIA(e) {
if (e) e.stopPropagation();
const panel = document.getElementById('aiPanel');
panel.classList.toggle('minimized');
const icono = document.getElementById('iconMin');
icono.className = panel.classList.contains('minimized') ? 'fas fa-chevron-up' : 'fas fa-minus';
}
function saludarIA() {
agregarMsg('bot', 'Hola, soy LIA. Pregúnteme cómo hacer algo en el sistema y le respondo con la documentación.');
// Atajos armados con los documentos que este usuario realmente puede ver
const quick = document.getElementById('aiQuick');
quick.innerHTML = DOCS.slice(0, 4)
.map(d => `<button class="ai-chip" onclick="enviarRapido('¿Qué explica ${esc(d.t)}?')">${esc(d.t)}</button>`)
.join('');
})();
}
function liaPreguntar(texto) {
document.getElementById('liaInput').value = texto;
liaEnviar();
function enviarRapido(texto) {
document.getElementById('aiInput').value = texto;
enviarIA();
}
function agregarMsg(tipo, html) {
const msgs = document.getElementById('aiMessages');
const div = document.createElement('div');
div.className = 'ai-msg ' + tipo;
div.innerHTML = html;
msgs.appendChild(div);
msgs.scrollTop = msgs.scrollHeight;
return div;
}
/* Formato mínimo de la respuesta: negritas, código, listas y saltos. */
function liaFmt(md) {
function mdToHtml(md) {
let h = esc(md);
h = h.replace(/```[\s\S]*?```/g, m => '<pre style="white-space:pre-wrap;font-size:.75rem">'
+ m.replace(/```\w*\n?/g, '') + '</pre>');
h = h.replace(/```[\s\S]*?```/g, m => '<pre>' + m.replace(/```\w*\n?/g, '') + '</pre>');
h = h.replace(/`([^`]+)`/g, '<code>$1</code>');
h = h.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
h = h.replace(/^\s*[-*]\s+(.*)$/gm, '<li>$1</li>');
@@ -375,57 +444,51 @@ function liaFmt(md) {
return h.replace(/\n{2,}/g, '<br><br>').replace(/\n/g, '<br>');
}
async function liaEnviar() {
const input = document.getElementById('liaInput');
const btn = document.getElementById('liaSend');
const msgs = document.getElementById('liaMsgs');
const q = input.value.trim();
if (!q) return;
async function enviarIA() {
const input = document.getElementById('aiInput');
const pregunta = input.value.trim();
if (!pregunta) return;
document.getElementById('liaSug')?.remove();
msgs.insertAdjacentHTML('beforeend', `<div class="lia-msg user">${esc(q)}</div>`);
const pensando = document.createElement('div');
pensando.className = 'lia-msg bot';
pensando.textContent = 'Buscando en la documentación…';
msgs.appendChild(pensando);
msgs.scrollTop = msgs.scrollHeight;
document.getElementById('aiQuick').innerHTML = '';
agregarMsg('user', esc(pregunta));
const typing = agregarMsg('bot typing', '');
input.value = '';
input.disabled = btn.disabled = true;
input.disabled = true;
try {
const r = await fetch(LIA_API, {
const res = await fetch(AI_API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pregunta: q, historial: _liaHist }),
body: JSON.stringify({ pregunta, historial: _liaHistorial }),
});
const d = await r.json();
const d = await res.json();
typing.className = 'ai-msg bot';
if (d.ok) {
pensando.innerHTML = liaFmt(d.respuesta);
typing.innerHTML = mdToHtml(d.respuesta);
if (d.fuentes && d.fuentes.length) {
msgs.insertAdjacentHTML('beforeend',
`<div class="lia-fuentes">Según: ${d.fuentes.map(esc).join(' · ')}</div>`);
const f = document.createElement('div');
f.className = 'ai-fuentes';
f.textContent = 'Según: ' + d.fuentes.join(' · ');
document.getElementById('aiMessages').appendChild(f);
}
_liaHist.push({ rol: 'user', texto: q }, { rol: 'model', texto: d.respuesta });
if (_liaHist.length > 6) _liaHist = _liaHist.slice(-6);
_liaHistorial.push({ rol: 'user', texto: pregunta }, { rol: 'model', texto: d.respuesta });
if (_liaHistorial.length > 6) _liaHistorial = _liaHistorial.slice(-6);
} else {
pensando.textContent = d.error || 'No pude responder.';
typing.textContent = d.error || 'No pude responder.';
}
} catch (_) {
pensando.textContent = 'Error de conexión.';
typing.className = 'ai-msg bot';
typing.textContent = 'Error de conexión';
} finally {
input.disabled = btn.disabled = false;
input.disabled = false;
input.focus();
const msgs = document.getElementById('aiMessages');
msgs.scrollTop = msgs.scrollHeight;
}
}
document.getElementById('liaInput').addEventListener('keydown', e => {
if (e.key === 'Enter') liaEnviar();
});
const inputQ = document.getElementById('q');
let _t = null;
inputQ.addEventListener('input', () => {
+1 -1
View File
@@ -201,7 +201,7 @@ DOCS;
// ── Prompt ────────────────────────────────────────────────────
$systemPrompt = <<<PROMPT
Eres LIA, la asistente inteligente del sistema de turnero del Laboratorio Clínico.
Responde en español, con el detalle que la pregunta requiera: si te piden un dato puntual sé breve, pero si te piden un listado, un análisis o una comparación, desarróllalo completo. Usa tablas o viñetas cuando ayuden a leer los datos.
Responde en español, tratando SIEMPRE de usted (nunca tú ni vos), con el detalle que la pregunta requiera: si te piden un dato puntual sé breve, pero si te piden un listado, un análisis o una comparación, desarróllalo completo. Usa tablas o viñetas cuando ayuden a leer los datos.
Puedes analizar: tiempos de espera y servicio por paciente, recepcionista o bacteriólogo; facturación del día; exámenes solicitados; franjas horarias con mayor demanda; buscar pacientes por nombre o cédula.
También respondes preguntas de cómo se usa o cómo funciona el sistema, apoyándote en la DOCUMENTACIÓN que se incluye más abajo cuando esté presente.
Solo usa la información del contexto proporcionado. Si no tienes el dato, dilo claramente y no lo inventes.