diff --git a/modules/soporte/DocIndex.php b/modules/soporte/DocIndex.php index db3db69..5451d1b 100644 --- a/modules/soporte/DocIndex.php +++ b/modules/soporte/DocIndex.php @@ -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, diff --git a/modules/soporte/api/ai_docs.php b/modules/soporte/api/ai_docs.php index eb535ac..31f7b6d 100644 --- a/modules/soporte/api/ai_docs.php +++ b/modules/soporte/api/ai_docs.php @@ -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. diff --git a/modules/soporte/docs/arquitectura/30-roles-y-permisos.md b/modules/soporte/docs/arquitectura/30-roles-y-permisos.md index 0b7b588..ad87a63 100644 --- a/modules/soporte/docs/arquitectura/30-roles-y-permisos.md +++ b/modules/soporte/docs/arquitectura/30-roles-y-permisos.md @@ -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, actualizá las dos a la vez: +Al cambiar el rol de alguien, actualice las dos a la vez: ```sql UPDATE admin_users diff --git a/modules/soporte/docs/arquitectura/40-modelo-de-datos.md b/modules/soporte/docs/arquitectura/40-modelo-de-datos.md index eaa0226..59fb456 100644 --- a/modules/soporte/docs/arquitectura/40-modelo-de-datos.md +++ b/modules/soporte/docs/arquitectura/40-modelo-de-datos.md @@ -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 diff --git a/modules/soporte/docs/arquitectura/60-decisiones-y-deuda.md b/modules/soporte/docs/arquitectura/60-decisiones-y-deuda.md index cbc97ea..c1e700d 100644 --- a/modules/soporte/docs/arquitectura/60-decisiones-y-deuda.md +++ b/modules/soporte/docs/arquitectura/60-decisiones-y-deuda.md @@ -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** → verificá 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. diff --git a/modules/soporte/docs/manual/10-primeros-pasos.md b/modules/soporte/docs/manual/10-primeros-pasos.md index 02e1f36..8309925 100644 --- a/modules/soporte/docs/manual/10-primeros-pasos.md +++ b/modules/soporte/docs/manual/10-primeros-pasos.md @@ -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) diff --git a/modules/soporte/docs/manual/15-kiosko-y-pantallas.md b/modules/soporte/docs/manual/15-kiosko-y-pantallas.md index 5508c25..8d29db9 100644 --- a/modules/soporte/docs/manual/15-kiosko-y-pantallas.md +++ b/modules/soporte/docs/manual/15-kiosko-y-pantallas.md @@ -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ó, verificá 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. diff --git a/modules/soporte/docs/manual/20-recepcion.md b/modules/soporte/docs/manual/20-recepcion.md index ad3ea4b..1f20da8 100644 --- a/modules/soporte/docs/manual/20-recepcion.md +++ b/modules/soporte/docs/manual/20-recepcion.md @@ -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 -Buscá 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 -Marcá **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. Verificá 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 diff --git a/modules/soporte/docs/manual/25-chat-whatsapp.md b/modules/soporte/docs/manual/25-chat-whatsapp.md index defcf01..4957399 100644 --- a/modules/soporte/docs/manual/25-chat-whatsapp.md +++ b/modules/soporte/docs/manual/25-chat-whatsapp.md @@ -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.** -Verificá 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. diff --git a/modules/soporte/docs/manual/30-toma-de-muestras.md b/modules/soporte/docs/manual/30-toma-de-muestras.md index ef37920..8b1f830 100644 --- a/modules/soporte/docs/manual/30-toma-de-muestras.md +++ b/modules/soporte/docs/manual/30-toma-de-muestras.md @@ -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. **Marcá 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. diff --git a/modules/soporte/docs/manual/40-enfermeros.md b/modules/soporte/docs/manual/40-enfermeros.md index 73b95af..cb60b08 100644 --- a/modules/soporte/docs/manual/40-enfermeros.md +++ b/modules/soporte/docs/manual/40-enfermeros.md @@ -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, actualizá 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. diff --git a/modules/soporte/docs/manual/50-administracion.md b/modules/soporte/docs/manual/50-administracion.md index ad85b8c..d43f862 100644 --- a/modules/soporte/docs/manual/50-administracion.md +++ b/modules/soporte/docs/manual/50-administracion.md @@ -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. diff --git a/modules/soporte/docs/operacion/10-runbook.md b/modules/soporte/docs/operacion/10-runbook.md index ef40ab6..d513b04 100644 --- a/modules/soporte/docs/operacion/10-runbook.md +++ b/modules/soporte/docs/operacion/10-runbook.md @@ -22,7 +22,7 @@ SELECT module_slug, permission FROM role_modules WHERE 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, actualizá 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" "" ``` -**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. Verificá 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 = ; diff --git a/modules/soporte/docs/operacion/20-configuraciones-criticas.md b/modules/soporte/docs/operacion/20-configuraciones-criticas.md index bf41037..cd1426d 100644 --- a/modules/soporte/docs/operacion/20-configuraciones-criticas.md +++ b/modules/soporte/docs/operacion/20-configuraciones-criticas.md @@ -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. diff --git a/modules/soporte/docs/operacion/30-despliegue.md b/modules/soporte/docs/operacion/30-despliegue.md index 09d9978..f70987b 100644 --- a/modules/soporte/docs/operacion/30-despliegue.md +++ b/modules/soporte/docs/operacion/30-despliegue.md @@ -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 verificá 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:///" ``` -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 diff --git a/modules/soporte/docs/operacion/40-respaldos.md b/modules/soporte/docs/operacion/40-respaldos.md index a3736b4..511cec9 100644 --- a/modules/soporte/docs/operacion/40-respaldos.md +++ b/modules/soporte/docs/operacion/40-respaldos.md @@ -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 -u -p 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. diff --git a/modules/soporte/docs/tecnica/70-catalogos.md b/modules/soporte/docs/tecnica/70-catalogos.md index 922d5fc..c68c3b8 100644 --- a/modules/soporte/docs/tecnica/70-catalogos.md +++ b/modules/soporte/docs/tecnica/70-catalogos.md @@ -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á. diff --git a/modules/soporte/docs/tecnica/90-endpoints.md b/modules/soporte/docs/tecnica/90-endpoints.md index 1079281..494eaf3 100644 --- a/modules/soporte/docs/tecnica/90-endpoints.md +++ b/modules/soporte/docs/tecnica/90-endpoints.md @@ -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. **Verificá 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 diff --git a/modules/soporte/views/documentacion.php b/modules/soporte/views/documentacion.php index f0f52d0..774b664 100644 --- a/modules/soporte/views/documentacion.php +++ b/modules/soporte/views/documentacion.php @@ -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; } }
@@ -269,23 +307,30 @@ Layout::open('Soporte · Documentación', 'fas fa-life-ring');
- - -
-
- -
LIA - Responde sobre la documentación que podés ver + + +
+
+ - +
+
LIA
+
Asistente de Documentación
+
+ +
-
-
Hola. Preguntame cómo hacer algo en el sistema y te respondo con la documentación.
-
-
-
- - +
+
+
+
@@ -338,36 +383,60 @@ function esc(s) { return d.innerHTML; } -/* ── Asistente LIA sobre la documentación ── */ -const LIA_API = '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 = '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 => ``) +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 => ``) .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 => '
'
-        + m.replace(/```\w*\n?/g, '') + '
'); + h = h.replace(/```[\s\S]*?```/g, m => '
' + m.replace(/```\w*\n?/g, '') + '
'); h = h.replace(/`([^`]+)`/g, '$1'); h = h.replace(/\*\*([^*]+)\*\*/g, '$1'); h = h.replace(/^\s*[-*]\s+(.*)$/gm, '
  • $1
  • '); @@ -375,57 +444,51 @@ function liaFmt(md) { return h.replace(/\n{2,}/g, '

    ').replace(/\n/g, '
    '); } -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', `
    ${esc(q)}
    `); - - 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', - `
    Según: ${d.fuentes.map(esc).join(' · ')}
    `); + 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', () => { diff --git a/modules/turnero/api/ai_chat.php b/modules/turnero/api/ai_chat.php index c9474f0..dc27da3 100644 --- a/modules/turnero/api/ai_chat.php +++ b/modules/turnero/api/ai_chat.php @@ -201,7 +201,7 @@ DOCS; // ── Prompt ──────────────────────────────────────────────────── $systemPrompt = <<