Commit Graph
407 Commits
Author SHA1 Message Date
Lizandro Guarnizo bbfe0417df up 2026-03-14 12:52:45 -05:00
Lizandro Guarnizo 405a3751e7 fix: T&C tab - quitar display:none y agregar case terms en loadTabData 2026-03-14 00:09:26 -05:00
Lizandro Guarnizo cf6743fbbd fix: corregir URL de upload binario a graph.facebook.com/{version}/{session_id} 2026-03-14 00:01:34 -05:00
Lizandro Guarnizo 9bdcdc91d8 fix: usar Resumable Upload API para foto de perfil WhatsApp 2026-03-14 00:00:26 -05:00
Lizandro Guarnizo 0b9744e9ef fix: eliminar cierre de funcion duplicado en app_simple.js 2026-03-13 23:57:30 -05:00
Lizandro Guarnizo 032219a4b6 fix: cerrar funcion toggleAdminUserStatus antes de las funciones de perfil WA 2026-03-13 23:55:40 -05:00
Lizandro Guarnizo 7df05ee3fb feat: perfil WhatsApp (foto, about, datos) y reset+sync de plantillas 2026-03-13 23:50:30 -05:00
Lizandro Guarnizo 290cdcc7ff fix: detectar rechazo T&C primero para evitar falso positivo con 'no acepto' 2026-03-13 23:39:39 -05:00
Lizandro Guarnizo 0987329a9b fix: sendWelcomeMessage no envía si términos no han sido aceptados 2026-03-13 23:36:51 -05:00
Lizandro Guarnizo 3d3c37fa18 fix: al aceptar T&C ir directo al menu, no enviar bienvenida por separado 2026-03-13 23:32:18 -05:00
Lizandro Guarnizo 94c6110e07 fix: welcome solo se envía si ya aceptó términos 2026-03-13 23:22:20 -05:00
Lizandro Guarnizo 95f83444a0 fix: system_config toma prioridad sobre terms_versions para mensajes T&C 2026-03-13 23:17:15 -05:00
Lizandro Guarnizo abfab22c35 fix: advisory lock por usuario en processMessage para eliminar race condition
- GET_LOCK('bot_user_ID', 10) serializa el procesamiento por usuario
- Solo un proceso a la vez puede ejecutar processMessage para el mismo user
- Recarga datos frescos del usuario tras adquirir el lock
- El lock se libera siempre en el bloque finally, incluso si hay excepciones
- Resuelve definitivamente el doble envío de bienvenida + términos
2026-03-13 19:33:31 -05:00
Lizandro Guarnizo bcfaf62293 fix: deduplicar webhooks duplicados con INSERT IGNORE + UNIQUE index en message_id
- conversations.message_id ahora tiene UNIQUE index (aplicado en BD prod)
- saveMessage() usa INSERT IGNORE: si el mismo wamid llega dos veces en
  paralelo solo una llamada procesa el mensaje y llama al bot
- La segunda llamada recibe rowCount=0 y hace continue sin pasar al bot
- Elimina race condition que enviaba bienvenida + términos simultáneamente
2026-03-13 19:22:34 -05:00
Lizandro Guarnizo 80e9cb43db fix: normalizar http/https al comparar URL del PDF para evitar duplicado 2026-03-13 19:16:10 -05:00
Lizandro Guarnizo 0e9c6935d8 fix: evitar doble saludo y URL duplicada en mensaje de términos
- sendTermsMessage: solo agrega PDF al final si la URL no está ya en el mensaje
- sendTermsMessage: marca welcome_sent_at=now() junto con terms_pending=1
  para evitar race condition donde un segundo webhook concurrente cae en
  isNewUser() y envía la bienvenida mientras los términos ya fueron enviados
2026-03-13 19:10:43 -05:00
Lizandro Guarnizo 46a9d97a07 fix: eliminar polling get_last_webhook_time (innecesario en index.php) 2026-03-13 18:47:21 -05:00
Lizandro Guarnizo 8df007d3de fix: actualizar APP_URL por defecto a bot.laboratorioximenacaicedo.com 2026-03-13 16:09:20 -05:00
Lizandro Guarnizo 730e1e9636 fix: accion ENUM en lab_autorizaciones al crear orden
El metodo registrarCambioEstado usaba estadoNuevo ('pendiente')
como valor de accion, pero pendiente no es un valor valido del
ENUM de lab_autorizaciones.accion.

Se pasa 'creada' explicitamente al crear una orden nueva, que si
es un valor valido del ENUM ('creada','en_revision','autorizada',
'rechazada','en_domicilio','completada','editada').
2026-03-13 15:53:42 -05:00
Lizandro Guarnizo 5211d83e8b fix: eliminar polling get_notifications en index.php + debug wamid en broadcast
- index.php: NotificationManager simplificado a stub vacío.
  Los elementos notification-bell / global-notification-toasts no existen en el
  markup → el setInterval cada 7s solo generaba tráfico inútil a get_notifications.php
  Las notificaciones ya se manejan vía SSE en conversations.php

- send_broadcast.php: exponer debug_wamids en respuesta (wamid, wa_status, wa_contact)
  para saber exactamente qué devuelve WhatsApp por cada destinatario.
  Agregar dryRun log del payload completo al error_log para diagnóstico de
  mensajes que WhatsApp acepta (200+wamid) pero no entrega.
2026-03-13 15:26:14 -05:00
Lizandro Guarnizo 6cdc7bd6c4 fix: corregir verificación de éxito en send_broadcast.php
La API de WhatsApp NO devuelve {success:true} - devuelve
{messaging_product, messages:[{id:wamid.xxx}], contacts:[...]}.
La comprobación anterior siempre fallaba → sent_count=0, error_count=N.
Ahora verifica presencia de messages/contacts en la respuesta.
2026-03-13 13:36:50 -05:00
Lizandro Guarnizo e081dc739a feat: soporte de imagen dinámica en plantillas WhatsApp
- Detecta header_type=IMAGE en get_template_details.php
- send_message (individual): muestra input de imagen si la plantilla lo requiere,
  sube a upload_media.php y envía header_image_url al backend
- broadcast (masivo): igual, con upload previo al envío
- api/send_message.php: acepta header_image_url y lo pasa como headerParameters
  [type:image, image:{link:URL}] a WhatsAppService::sendTemplateMessage()
- api/send_broadcast.php: igual para cada destinatario del masivo
- Preview en tiempo real de la imagen seleccionada en ambos formularios
2026-03-13 13:24:22 -05:00
Lizandro Guarnizo 027c8a6f26 fix: tipo_servicio hardcodeado como 'domicilio' en portal enfermero 2026-03-13 13:15:03 -05:00
Lizandro Guarnizo 7cc29fac1e fix: quitar campo Tipo de servicio del portal enfermero 2026-03-13 13:12:00 -05:00
Lizandro Guarnizo 4bcfd585cd fix: enf-nombre muestra nombre real desde sesión PHP (no queda en Cargando…) 2026-03-13 09:03:31 -05:00
Lizandro Guarnizo b8b4bffde2 feat: sesiones minimizadas múltiples — píldoras con nombre de chat y navegación 2026-03-13 08:57:10 -05:00
Lizandro Guarnizo e9c8cf7d9b feat: crear nuevo paciente desde modal de agendamiento (enfermero portal) 2026-03-12 23:18:31 -05:00
Lizandro Guarnizo 2b46057ac1 feat: modal agendar domicilio es minimizable y arrastrable 2026-03-12 23:16:14 -05:00
Lizandro Guarnizo 5c4ddd1cd8 fix: portal enfermero — agendamiento siempre particular, sin campo seguro 2026-03-12 23:04:34 -05:00
Lizandro Guarnizo 702323f852 fix: corrección portal enfermero — pagos y agendamiento
- fix: portal.renderAgenda → portal._renderLista() (error JS al registrar pago)
- feat: modal nueva agenda añade tipo_cliente (particular/seguro), seguro_nombre, autorizacion, valor_domicilio, valor_copago
- feat: toggleSeguro() muestra/oculta campo EPS según tipo seleccionado
- fix: abrir() resetea todos los campos nuevos en cada apertura del modal
- datos de cobro se envían al guardar y se auto-asigna la enfermera creadora
2026-03-12 23:02:48 -05:00
Lizandro Guarnizo eaebda9fe7 feat: edición completa de domicilio desde panel detalle
- Modal expandido: tipo_cliente, seguro, autorizacion, valor_domicilio, valor_copago, copago_laboratorio, indicaciones, fecha, hora, tipo_servicio, notas
- Botón Editar en header del panel detalle (aparece al seleccionar un domicilio)
- editarDomicilio(id): carga y pre-rellena todos los campos del modal
- toggleSeguro(): muestra/oculta campo seguro_nombre según tipo_cliente
- Tras guardar, el detalle se refresca automáticamente
2026-03-12 23:00:25 -05:00
Lizandro Guarnizo beb1bbe1d4 feat: sistema de pagos domicilios — cobro enfermero y estado pago admin
- database/07_add_pago_domicilio.sql: 6 columnas nuevas en lab_domicilios
  (pago_estado ENUM, pago_modo, pago_monto, pago_fecha, pago_notas, pago_registrado_por)
- Domicilio::filtrarCampos(): permite guardar campos de pago vía actualizar()
- Enfermera::agenda(): incluye valor_domicilio, valor_copago, pago_estado, pago_modo, pago_monto, pago_notas
- api/lab/registrar_pago.php: endpoint POST para registrar/actualizar pago del domicilio
- enfermero_portal.php: bloque cobro en card, modal registrar pago (efectivo/transferencia/exento), objeto pagoModal JS
- lab_domicilios.php: badge pago //🔵 en fila tabla, sección pago detallada en panel lateral
2026-03-12 22:53:24 -05:00
Lizandro Guarnizo c42e0976d7 fix: enfermero_portal - mostrar imagen de la orden aunque no esté vinculada al domicilio
Si lab_domicilios.orden_id es NULL (domicilio creado antes del fix
o desde otro flujo), usa subquery para encontrar la orden más reciente
del mismo paciente que tenga local_file.

Así el enfermero siempre ve la imagen de la orden del paciente
independientemente de cómo se agendó el domicilio.
2026-03-12 22:38:48 -05:00
Lizandro Guarnizo 8956de8eaa fix: ruta doble en local_file al mostrar imagen de orden médica
La BD guarda local_file con 'uploads/media/...' incluido.
El código le añadía otro 'uploads/media/' dando ruta doble.

- lab_ordenes.php: helper _resolveImg() detecta si el path
  ya empieza con 'uploads/' y no añade el prefijo de nuevo
- enfermero_portal.php: nueva función resImg() con misma lógica,
  usada en _cardHTML() al renderizar la imagen de la orden
2026-03-12 22:23:15 -05:00
Lizandro Guarnizo d362598a70 feat: relacionar imagen de WhatsApp con orden médica al agendar domicilio
Al abrir el modal desde una imagen en el chat:
- guardar() crea primero la OrdenMedica via crear_desde_whatsapp.php
  y pasa el orden_id resultante a save_domicilio.php
- Enfermera::agenda() hace LEFT JOIN a lab_ordenes_medicas para traer
  local_file de la orden asociada (as orden_local_file)
- enfermero_portal._cardHTML() muestra la imagen de la orden médica
  con link para ampliar
2026-03-12 22:14:04 -05:00
Lizandro Guarnizo 12e7b411d7 feat: enfermero_portal - mostrar todos los datos del paciente en tarjeta de domicilio
La tarjeta ahora muestra: documento, fecha de nacimiento, EPS,
teléfono (clickeable), correo (clickeable), exámenes solicitados
y tipo de cliente (particular/seguro con nombre del seguro).

Cambios:
- Enfermera::agenda() amplía SELECT con email, fecha_nacimiento,
  numero_documento, tipo_documento, eps, examenes_solicitados,
  tipo_cliente, seguro_nombre, ciudad, indicaciones_dir, notas_admin
- my_agenda.php simplificado: ya no re-consulta lab_domicilios por
  los campos que ahora vienen directo en la query
- enfermero_portal.php _cardHTML() muestra todos los campos nuevos
2026-03-12 22:05:47 -05:00
Lizandro Guarnizo bce9a8c6b5 feat: modalAgendarDomicilio - campos fecha nacimiento y correo con auto-relleno 2026-03-12 19:58:47 -05:00
Lizandro Guarnizo 4bbb31dc30 fix: lab_usuarios.php — tab-content oculto por CSS custom vs Bootstrap 5
styles.css define .tab-content { display:none } para un sistema de tabs
propio. Bootstrap 5 agrega 'active' al .tab-pane, no al .tab-content,
por lo que el contenedor nunca se mostraba (display:none permanente).

Solución: style='display:block;padding:0' en el div .tab-content para
que Bootstrap controle la visibilidad de los panes internos.
2026-03-12 18:57:39 -05:00
Lizandro Guarnizo 3399d3cf41 fix: docker-compose.prod.yml usar red coolify externa (Coolify usa este archivo, no docker-compose.yml)
Coolify usa /artifacts/.../docker-compose.prod.yml en el deploy.
Ese archivo tenía 'whatsapp-network' local bridge — el contenedor nunca
se conectaba a la red 'coolify' donde está el Redis → SERVFAIL en DNS.

Cambios:
- networks: whatsapp-network → coolify (external: true)
- container_name: removido (Coolify asigna el suyo)
- healthcheck: timeout 5s→10s, start_period 30s→60s
2026-03-12 18:48:58 -05:00
Lizandro Guarnizo 729474b138 fix: PHP sessions → Redis correcto (session.save_path con host real)
php.ini NO expande variables de entorno, estaba hardcodeado 'tcp://redis:6379'.
PHP intentaba conectar al host inexistente 'redis' en cada request →
timeout 10s en session_start() → todos los scripts tardaban 10-13s.

Solución:
- entrypoint.sh reescribe session.save_path con el host/password real al inicio
- php.ini actualizado con placeholder (sobreescrito por entrypoint de todas formas)
2026-03-12 17:13:11 -05:00
Lizandro Guarnizo d6d312f72f fix: health.php respuesta instantánea — sin DB ni Redis
Versión anterior intentaba conectar a DB (g1286...) y Redis (zqmfom0...)
que colgaban esperando conexión → todos los workers PHP-FPM bloqueados →
nginx acepta TCP pero no responde HTTP → healthcheck timeout → unhealthy.
2026-03-12 17:04:40 -05:00
Lizandro Guarnizo 6807da8a2b fix: conectar app a red 'coolify' donde vive Redis de Coolify
El contenedor Redis (zqmfom0vw6nj3ae66mkwxek1) vive en la red 'coolify'.
La app estaba solo en la red nj05x5... → DNS SERVFAIL → health.php tardaba
10-12s intentando conectar → healthcheck timeout → unhealthy → 'no available server'.

Solución: declarar red coolify como external y conectar el servicio app.
Fix inmediato aplicado manualmente con: docker network connect coolify <container>
2026-03-12 16:58:44 -05:00
Lizandro Guarnizo a497dc4b31 fix: migración SQL + red Docker para Coolify
06_master_sync.sql:
- Comentario tenía ';' (La columna role_id ya existe; la FK) →
  el parser de migrate.php divide por ; y el texto 'la FK' quedaba
  como sentencia SQL → error SQLSTATE[42000]

docker-compose.yml:
- Eliminar whatsapp-network (red bridge propia)
- Con 1 solo servicio no se necesita red interna
- Coolify añade automáticamente su red nj05x5... (donde está Traefik)
- La red bridge extra podría interferir con el routing de Traefik
2026-03-12 16:42:14 -05:00
Lizandro Guarnizo 2537bc8cfc fix: healthcheck nunca retorna 503 — evita 'no available server'
health.php:
- DB fallo → 'warning' (no 'unhealthy', no cierra tráfico Traefik)
- Redis ya era warning desde commit anterior

docker-compose.yml:
- timeout: 5s → 10s (DB externa puede tardar más)
- start_period: 40s → 60s (tiempo suficiente para that migrations corran)
- fallback: si health.php falla, intenta / raíz antes de marcar unhealthy
2026-03-12 16:35:37 -05:00
Lizandro Guarnizo 3b85c4ac93 fix: docker-compose.yml limpiar volúmenes y container_name para Coolify
- Eliminar container_name (Coolify asigna su propio nombre, conflicto evitado)
- Eliminar ./:/var/www/html (bind mount sobreescribía código del contenedor en prod)
- Eliminar ./logs y ./uploads bind mounts (reemplazados por named volumes)
- Eliminar php-socket volume (ya no se usa)
- Usar named volumes app-logs/app-uploads (persistencia correcta en Coolify)
2026-03-12 16:32:46 -05:00
Lizandro Guarnizo 85fb463708 fix: 06_master_sync.sql corregir errores SQL para MariaDB
1. ADD CONSTRAINT IF NOT EXISTS no es estándar en MariaDB < 10.5 —
   eliminado el bloque FK de admin_users.role_id (la integridad la
   gestiona la aplicación).
2. AFTER `pdf_path` fallaba porque la columna no existe en lab_form_envios —
   eliminado el AFTER, la columna se agrega al final de la tabla.

Esto evita que la migración falle en cada deploy.
2026-03-12 16:30:03 -05:00
Lizandro Guarnizo ec2135442d fix: eliminar wait de Redis en entrypoint.sh
Redis es externo (Coolify managed) — esperar con redis-cli causaba
30 segundos de delay en cada arranque porque:
1. redis-cli no pasaba -a $REDIS_PASSWORD (auth fallaba)
2. Redis no es local, no tiene sentido hacer ping desde dentro del contenedor
La app conecta a Redis cuando necesita (RedisQueue, ConversationState, etc.)
2026-03-12 16:26:46 -05:00
Lizandro Guarnizo 9571b2677a fix: health.php Redis fallo no marca app como unhealthy
Redis es infraestructura opcional — si falla la conexión, retorna 'warning'
en lugar de 'unhealthy', evitando que Traefik muestre 'no available server'.
Solo DB down debe marcar el app como unhealthy (es crítico).
2026-03-12 16:22:43 -05:00
Lizandro Guarnizo 2a50a5e1d4 fix: crear .installation_completed (con punto) en Dockerfile
isInstallationCompleted() en config.php busca '.installation_completed' (con punto)
pero el Dockerfile solo creaba 'installation_completed' (sin punto).
Ahora se crean ambas variantes para cubrir todos los checks del código.
2026-03-12 16:09:23 -05:00
Lizandro Guarnizo 30e38720e2 fix: eliminar REDIS_HOST hardcodeado 'redis' en todos los archivos
- docker-compose.prod.yml: REDIS_HOST=redis → ${REDIS_HOST:-127.0.0.1}
  eliminados servicios redis/worker/maintenance (usan Coolify/Supervisor)
- .env, .env.docker: REDIS_HOST=redis → 127.0.0.1 (fallback seguro)
- .env.example: actualizado con comentario de Coolify
- health.php: fallback 'redis' → '127.0.0.1'
- PHP files (RedisQueue, ConversationState, etc.) ya usaban getenv() correctamente
2026-03-12 13:48:01 -05:00