const { useEffect, useMemo, useState, useCallback, createContext, useContext, useRef } = React;

const AppContext = createContext(null);
const RouterContext = createContext(null);

const NAV_ITEMS = [
  { to: '/patients', label: 'Niños', moduleKey: 'patients' },
  { to: '/documents', label: 'Fichas', moduleKey: 'documents' },
  { to: '/sessions', label: 'Sesiones', moduleKey: 'sessions' },
  { to: '/notifications', label: 'Notificaciones', moduleKey: 'notifications' },
  { to: '/therapies', label: 'Terapias', roles: ['root', 'admin'], moduleKey: 'therapies' },
  { to: '/assignments', label: 'Asignaciones', roles: ['root', 'admin'], moduleKey: 'assignments' },
  { to: '/users', label: 'Usuarios', roles: ['root', 'admin'], moduleKey: 'users' },
  { to: '/billing', label: 'Facturacion', roles: ['root', 'admin'], moduleKey: 'billing' },
  { to: '/meeting-minutes', label: 'Actas', roles: ['root', 'admin'], moduleKey: 'meeting_minutes' },
];

const MONTH_OPTIONS = [
  { value: '01', label: 'Enero' },
  { value: '02', label: 'Febrero' },
  { value: '03', label: 'Marzo' },
  { value: '04', label: 'Abril' },
  { value: '05', label: 'Mayo' },
  { value: '06', label: 'Junio' },
  { value: '07', label: 'Julio' },
  { value: '08', label: 'Agosto' },
  { value: '09', label: 'Septiembre' },
  { value: '10', label: 'Octubre' },
  { value: '11', label: 'Noviembre' },
  { value: '12', label: 'Diciembre' },
];

const QUARTER_OPTIONS = [
  { value: '1', label: '1er trimestre (Ene-Mar)' },
  { value: '2', label: '2º trimestre (Abr-Jun)' },
  { value: '3', label: '3er trimestre (Jul-Sep)' },
  { value: '4', label: '4º trimestre (Oct-Dic)' },
];

const SEMESTER_OPTIONS = [
  { value: '1', label: '1er semestre (Ene-Jun)' },
  { value: '2', label: '2º semestre (Jul-Dic)' },
];

async function api(path, options = {}) {
  const request = {
    method: options.method || 'GET',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json',
      ...(options.headers || {}),
    },
  };

  if (options.body !== undefined) {
    request.body = JSON.stringify(options.body);
  }

  const response = await fetch(`/api${path}`, request);
  const data = await response.json().catch(() => ({}));

  if (!response.ok) {
    const error = new Error(data.message || 'Error en la solicitud.');
    error.status = response.status;
    error.payload = data;
    throw error;
  }

  return data;
}

function formatDate(value) {
  if (!value) {
    return '-';
  }

  return new Date(value).toLocaleDateString('es-ES');
}

function getYearFromDateString(value) {
  if (!value) {
    return 'Sin ano';
  }

  const parsed = new Date(value);
  if (Number.isNaN(parsed.getTime())) {
    return 'Sin ano';
  }

  return String(parsed.getFullYear());
}

function getMonthFromDateString(value) {
  if (!value) {
    return null;
  }

  const parsed = new Date(value);
  if (Number.isNaN(parsed.getTime())) {
    return null;
  }

  return String(parsed.getMonth() + 1).padStart(2, '0');
}

function getQuarterFromDateString(value) {
  const month = getMonthFromDateString(value);
  if (!month) {
    return null;
  }

  return String(Math.ceil(Number(month) / 3));
}

function getSemesterFromDateString(value) {
  const month = getMonthFromDateString(value);
  if (!month) {
    return null;
  }

  return Number(month) <= 6 ? '1' : '2';
}

function formatDateTime(value) {
  if (!value) {
    return '-';
  }

  const isoLike = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(value) ? `${value.replace(' ', 'T')}Z` : value;
  const parsed = new Date(isoLike);

  if (Number.isNaN(parsed.getTime())) {
    return '-';
  }

  return parsed.toLocaleString('es-ES', {
    day: '2-digit',
    month: '2-digit',
    year: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
  });
}

function normalizeSessionDate(value) {
  const raw = String(value || '').trim();
  if (!raw) {
    return null;
  }

  if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
    return raw;
  }

  const match = raw.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
  if (!match) {
    return null;
  }

  const day = Number(match[1]);
  const month = Number(match[2]);
  const year = Number(match[3]);
  const candidate = new Date(year, month - 1, day);

  if (
    candidate.getFullYear() !== year ||
    candidate.getMonth() !== month - 1 ||
    candidate.getDate() !== day
  ) {
    return null;
  }

  return `${String(year)}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
}

function normalizeSessionTime(value) {
  const raw = String(value || '').trim();
  if (!raw) {
    return null;
  }

  const match = raw.match(/^(\d{1,2}):(\d{2})$/);
  if (!match) {
    return null;
  }

  const hour = Number(match[1]);
  const minute = Number(match[2]);

  if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
    return null;
  }

  return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
}

function formatTime(value) {
  const normalized = normalizeSessionTime(value);
  return normalized || '-';
}

function formatCurrency(value) {
  const amount = Number(value || 0);
  return new Intl.NumberFormat('es-ES', {
    style: 'currency',
    currency: 'EUR',
  }).format(amount);
}

function calculateBillingAmounts(amountCents, taxRate) {
  const baseAmount = Number(amountCents || 0) / 100;
  const safeTaxRate = Number.isFinite(Number(taxRate)) ? Number(taxRate) : 0;
  const taxAmount = baseAmount * (safeTaxRate / 100);
  const totalAmount = baseAmount + taxAmount;

  return {
    baseAmount,
    taxRate: safeTaxRate,
    taxAmount,
    totalAmount,
  };
}

const SPANISH_TAX_CATALOG = [
  {
    region: 'España peninsular y Baleares (IVA)',
    taxName: 'IVA',
    rates: [
      { rate: '21.00', label: 'General 21%' },
      { rate: '10.00', label: 'Reducido 10%' },
      { rate: '4.00', label: 'Superreducido 4%' },
      { rate: '0.00', label: 'Exento 0%' },
    ],
  },
  {
    region: 'Canarias (IGIC)',
    taxName: 'IGIC',
    rates: [
      { rate: '7.00', label: 'General 7%' },
      { rate: '3.00', label: 'Reducido 3%' },
      { rate: '15.00', label: 'Incrementado 15%' },
      { rate: '0.00', label: 'Tipo cero 0%' },
    ],
  },
  {
    region: 'Ceuta y Melilla (IPSI)',
    taxName: 'IPSI',
    rates: [
      { rate: '4.00', label: 'General 4%' },
      { rate: '1.00', label: 'Reducido 1%' },
      { rate: '10.00', label: 'Incrementado 10%' },
      { rate: '0.00', label: 'Exento 0%' },
    ],
  },
  {
    region: 'Sin impuesto',
    taxName: 'Sin impuesto',
    rates: [{ rate: '0.00', label: 'Exenta' }],
  },
];

function taxOptionValue(taxName, taxRate) {
  const normalizedRate = Number.isFinite(Number(taxRate)) ? Number(taxRate).toFixed(2) : '0.00';
  return `${taxName || ''}|${normalizedRate}`;
}

function TaxTypeSelect({ taxName, taxRate, onSelect }) {
  const currentValue = taxOptionValue(taxName, taxRate);
  const isKnownValue = SPANISH_TAX_CATALOG.some((group) =>
    group.rates.some((option) => taxOptionValue(group.taxName, option.rate) === currentValue)
  );

  return (
    <select
      value={currentValue}
      onChange={(event) => {
        const [nextTaxName, nextTaxRate] = event.target.value.split('|');
        onSelect(nextTaxName, nextTaxRate);
      }}
    >
      {!isKnownValue ? (
        <option value={currentValue}>{`${taxName || 'Sin impuesto'} ${Number(taxRate || 0).toFixed(2)}% (personalizado)`}</option>
      ) : null}
      {SPANISH_TAX_CATALOG.map((group) => (
        <optgroup key={group.region} label={group.region}>
          {group.rates.map((option) => (
            <option key={taxOptionValue(group.taxName, option.rate)} value={taxOptionValue(group.taxName, option.rate)}>
              {group.taxName} - {option.label}
            </option>
          ))}
        </optgroup>
      ))}
    </select>
  );
}

const ACCOUNTING_CATEGORY_OPTIONS = {
  income: ['Cuotas familias', 'Subvencion', 'Donacion', 'Actividad', 'Factura familia', 'Otro ingreso'],
  expense: ['Nominas', 'Alquiler', 'Material', 'Servicios profesionales', 'Suministros', 'Seguros', 'Otro gasto'],
};

function escapeHtml(value) {
  return String(value || '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function useApp() {
  return useContext(AppContext);
}

function useRouter() {
  return useContext(RouterContext);
}

function RouterProvider({ children }) {
  const [pathname, setPathname] = useState(window.location.pathname || '/login');

  useEffect(() => {
    const handler = () => setPathname(window.location.pathname || '/login');
    window.addEventListener('popstate', handler);
    return () => window.removeEventListener('popstate', handler);
  }, []);

  const navigate = useCallback((to, options = {}) => {
    const current = window.location.pathname;
    if (current === to) {
      setPathname(to);
      return;
    }

    if (options.replace) {
      window.history.replaceState({}, '', to);
    } else {
      window.history.pushState({}, '', to);
    }

    setPathname(to);
  }, []);

  const value = useMemo(() => ({ pathname, navigate }), [pathname, navigate]);

  return <RouterContext.Provider value={value}>{children}</RouterContext.Provider>;
}

function Link({ to, className, children }) {
  const { navigate } = useRouter();

  return (
    <a
      href={to}
      className={className}
      onClick={(event) => {
        event.preventDefault();
        navigate(to);
      }}
    >
      {children}
    </a>
  );
}

function IconActionButton({ icon, label, onClick, tone = 'ghost', disabled = false }) {
  const className = tone === 'danger'
    ? 'button button-danger action-icon-button'
    : tone === 'success'
      ? 'button button-success action-icon-button'
      : 'button button-ghost action-icon-button';

  return (
    <button type="button" className={className} onClick={onClick} disabled={disabled} aria-label={label} title={label}>
      {icon === 'delete' ? (
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
          <path d="M9 3h6l1 2h4v2H4V5h4l1-2zm1 6h2v9h-2V9zm4 0h2v9h-2V9zM7 9h2v9H7V9z" />
        </svg>
      ) : icon === 'download' ? (
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
          <path d="M11 4h2v8l3-3 1.4 1.4L12 16l-5.4-5.6L8 9l3 3V4zm-5 14h12v2H6v-2z" />
        </svg>
      ) : icon === 'print' ? (
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
          <path d="M7 3h10v4H7V3zm10 8h1a2 2 0 0 1 2 2v5h-3v3H7v-3H4v-5a2 2 0 0 1 2-2h1v3h10v-3zm-2 8v-5H9v5h6z" />
        </svg>
      ) : icon === 'add' ? (
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
          <path d="M19 11H13V5h-2v6H5v2h6v6h2v-6h6z" />
        </svg>
      ) : icon === 'thumb-up' ? (
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
          <path d="M2 21h3V10H2v11zm19.83-9.29c.11-.25.17-.52.17-.8V9.5c0-1.1-.9-2-2-2h-5.5l.83-4.04.03-.32c0-.41-.17-.79-.44-1.06L13.83 1 7.41 7.42C7.05 7.78 6.85 8.25 6.85 8.75V19.5c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.14-7.57z" />
        </svg>
      ) : icon === 'thumb-down' ? (
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
          <path d="M22 3h-3v11h3V3zM2.17 12.29c-.11.25-.17.52-.17.8v1.41c0 1.1.9 2 2 2h5.5l-.83 4.04-.03.32c0 .41.17.79.44 1.06L10.17 23l6.42-6.42c.36-.36.56-.83.56-1.33V4.5c0-1.1-.9-2-2-2h-9c-.83 0-1.54.5-1.84 1.22L1.17 11.29z" />
        </svg>
      ) : (
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
          <path d="M3 17.25V21h3.75L17.8 9.94l-3.75-3.75L3 17.25zm14.71-9.04a1 1 0 0 0 0-1.41L15.2 4.29a1 1 0 0 0-1.41 0L12.38 5.7l3.75 3.75 1.58-1.24z" />
        </svg>
      )}
    </button>
  );
}

function AppProvider({ children }) {
  const [session, setSession] = useState({
    loading: true,
    authenticated: false,
    user: null,
    moduleSettings: {},
    orgSettings: {},
    homePath: '/dashboard',
  });
  const [notice, setNotice] = useState(null);

  const notify = useCallback((type, message) => {
    setNotice({ type, message });
  }, []);

  const refreshSession = useCallback(async () => {
    try {
      const data = await api('/session');

      if (!data.authenticated) {
        setSession({
          loading: false,
          authenticated: false,
          user: null,
          moduleSettings: {},
          orgSettings: {},
          homePath: '/dashboard',
        });
        return;
      }

      setSession({
        loading: false,
        authenticated: true,
        user: data.user,
        moduleSettings: data.moduleSettings || {},
        orgSettings: data.orgSettings || {},
        homePath: data.homePath || '/dashboard',
      });
    } catch (error) {
      setSession({
        loading: false,
        authenticated: false,
        user: null,
        moduleSettings: {},
        orgSettings: {},
        homePath: '/dashboard',
      });
    }
  }, []);

  useEffect(() => {
    refreshSession();
  }, [refreshSession]);

  const value = useMemo(
    () => ({
      session,
      setSession,
      refreshSession,
      notice,
      setNotice,
      notify,
    }),
    [session, refreshSession, notice, notify]
  );

  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}

function Redirect({ to, replace = true }) {
  const { navigate } = useRouter();

  useEffect(() => {
    navigate(to, { replace });
  }, [navigate, to, replace]);

  return null;
}

function AppShell() {
  const { session, notice, setNotice } = useApp();
  const { pathname } = useRouter();

  useEffect(() => {
    if (!notice) {
      return undefined;
    }

    const timer = setTimeout(() => setNotice(null), 3500);
    return () => clearTimeout(timer);
  }, [notice, setNotice]);

  if (session.loading) {
    return <div className="loader-screen">Cargando...</div>;
  }

  const isAuthPage = pathname === '/login';

  return (
    <div className={`page-shell ${session.authenticated ? 'app-shell' : 'auth-shell'}`}>
      {session.authenticated && !isAuthPage ? <MainHeader /> : null}
      <main className={`main-content ${!session.authenticated ? 'centered' : ''}`}>
        {notice ? <div className={`flash flash-${notice.type}`}>{notice.message}</div> : null}
        <RouteView />
      </main>
    </div>
  );
}

function RouteView() {
  const { session } = useApp();
  const { pathname } = useRouter();

  if (pathname === '/login') {
    if (session.authenticated) {
      return <Redirect to={session.homePath} />;
    }

    return <LoginPage />;
  }

  return (
    <Protected>
      {pathname === '/dashboard' ? (
        <Protected roles={['root', 'admin']}>
          <DashboardPage />
        </Protected>
      ) : pathname === '/patients' ? (
        <Protected moduleKey="patients">
          <PatientsPage />
        </Protected>
      ) : pathname === '/documents' ? (
        <Protected moduleKey="documents">
          <DocumentsPage />
        </Protected>
      ) : pathname === '/sessions' ? (
        <Protected moduleKey="sessions">
          <SessionsPage />
        </Protected>
      ) : pathname === '/notifications' ? (
        <Protected moduleKey="notifications">
          <NotificationsPage />
        </Protected>
      ) : pathname === '/therapies' ? (
        <Protected roles={['root', 'admin']} moduleKey="therapies">
          <TherapiesPage />
        </Protected>
      ) : pathname === '/assignments' ? (
        <Protected roles={['root', 'admin']} moduleKey="assignments">
          <AssignmentsPage />
        </Protected>
      ) : pathname === '/users' ? (
        <Protected roles={['root', 'admin']} moduleKey="users">
          <UsersPage />
        </Protected>
      ) : pathname === '/profile' ? (
        <ProfilePage />
      ) : pathname === '/billing' ? (
        <Protected roles={['root', 'admin']} moduleKey="billing">
          <BillingPage />
        </Protected>
      ) : pathname === '/meeting-minutes' ? (
        <Protected roles={['root', 'admin']} moduleKey="meeting_minutes">
          <MeetingMinutesPage />
        </Protected>
      ) : (
        <Redirect to={session.homePath} />
      )}
    </Protected>
  );
}

function Protected({ children, roles, moduleKey }) {
  const { session } = useApp();

  if (!session.authenticated) {
    return <Redirect to="/login" />;
  }

  if (roles && !roles.includes(session.user.role)) {
    return <Redirect to={session.homePath} />;
  }

  if (moduleKey && !session.moduleSettings[moduleKey]) {
    return <Redirect to={session.homePath} />;
  }

  return children;
}

function MainHeader() {
  const { session, refreshSession, notify } = useApp();
  const { pathname, navigate } = useRouter();
  const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
  const [isMobileNavOpen, setIsMobileNavOpen] = useState(false);
  const [notificationCount, setNotificationCount] = useState(0);
  const userMenuRef = useRef(null);
  const headerRef = useRef(null);
  const canAccessDashboard = session.user.role === 'root' || session.user.role === 'admin';

  const visibleNavItems = NAV_ITEMS.filter((item) => {
    const roleOk = !item.roles || item.roles.includes(session.user.role);
    const moduleOk = !item.moduleKey || session.moduleSettings[item.moduleKey];
    return roleOk && moduleOk;
  });

  const byPath = Object.fromEntries(visibleNavItems.map((item) => [item.to, item]));
  const dashboardLink = canAccessDashboard ? { to: '/dashboard', label: 'Dashboard' } : null;

  const adminMainLinks = [
    dashboardLink,
    byPath['/users'],
    byPath['/patients'],
    byPath['/therapies'],
    byPath['/assignments'],
    byPath['/meeting-minutes'],
  ].filter(Boolean);

  const panelAdminLinks = [
    canAccessDashboard ? { to: '/dashboard', label: 'Panel Admin' } : null,
    byPath['/billing'],
  ].filter(Boolean);

  const therapistPanelLinks = [
    byPath['/sessions'],
    byPath['/documents'],
  ].filter(Boolean);

  const utilityLinks = [
    byPath['/notifications'],
    { to: '/profile', label: 'Mi Perfil' },
  ].filter(Boolean);

  const buildIcon = (to) => {
    if (to === '/patients') {
      return <path d="M12 12a4 4 0 1 0-4-4 4 4 0 0 0 4 4zm0 2c-3.33 0-6 1.8-6 4v1h12v-1c0-2.2-2.67-4-6-4z" />;
    }
    if (to === '/documents') {
      return <path d="M6 2h8l4 4v16H6V2zm8 1.5V7h3.5" />;
    }
    if (to === '/sessions') {
      return <path d="M7 2h2v2h6V2h2v2h3v18H4V4h3V2zm11 8H6v10h12V10z" />;
    }
    if (to === '/notifications') {
      return <path d="M12 2a6 6 0 0 0-6 6v3.35L4.26 14.25A1 1 0 0 0 5.12 16h13.76a1 1 0 0 0 .86-1.75L18 11.35V8a6 6 0 0 0-6-6zm0 20a3 3 0 0 0 2.82-2H9.18A3 3 0 0 0 12 22z" />;
    }
    if (to === '/users') {
      return <path d="M16 11a4 4 0 1 0-3.99-4A4 4 0 0 0 16 11zM8 12a3 3 0 1 0-3-3 3 3 0 0 0 3 3zm8 1c-2.67 0-8 1.33-8 4v2h16v-2c0-2.67-5.33-4-8-4zM8 13c-2.67 0-8 1.33-8 4v2h6v-2a5.77 5.77 0 0 1 2.17-4.42A8.88 8.88 0 0 0 8 13z" />;
    }
    if (to === '/therapies') {
      return <path d="m4 12 4 4 12-12-1.5-1.5L8 13 5.5 10.5 4 12z" />;
    }
    if (to === '/assignments') {
      return <path d="M7 6h10v2H7V6zm0 5h10v2H7v-2zm0 5h7v2H7v-2z" />;
    }
    if (to === '/billing') {
      return <path d="M3 6h18v12H3V6zm2 2v8h14V8H5zm4 2h6v2H9v-2z" />;
    }
    if (to === '/meeting-minutes') {
      return <path d="M9 2h6v2h3a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h3V2zm-1 4v14h8V6H8zm2 3h4v2h-4V9zm0 4h4v2h-4v-2z" />;
    }
    if (to === '/dashboard') {
      return <path d="M4 13h7V4H4v9zm0 7h7v-5H4v5zm9 0h7V11h-7v9zm0-16v5h7V4h-7z" />;
    }
    if (to === '/profile') {
      return <path d="M12 12a4 4 0 1 0-4-4 4 4 0 0 0 4 4zm0 2c-3.33 0-6 1.8-6 4v2h12v-2c0-2.2-2.67-4-6-4z" />;
    }

    return <path d="M4 12h16v2H4z" />;
  };

  const renderNavLink = (item, extra = null) => (
    <Link
      key={item.to}
      className={`header-link ${pathname.startsWith(item.to) ? 'active' : ''}`}
      to={item.to}
    >
      <span className="header-link-inner">
        <span className="header-link-icon" aria-hidden="true">
          <svg viewBox="0 0 24 24" focusable="false">
            {buildIcon(item.to)}
          </svg>
        </span>
        <span>{item.label}</span>
        {extra}
      </span>
    </Link>
  );

  const onLogout = async () => {
    try {
      await api('/auth/logout', { method: 'POST' });
      await refreshSession();
      navigate('/login', { replace: true });
    } catch (error) {
      notify('error', error.message || 'No se pudo cerrar sesion.');
    }
  };

  useEffect(() => {
    const handleDocumentClick = (event) => {
      if (!userMenuRef.current) {
        return;
      }

      if (!userMenuRef.current.contains(event.target)) {
        setIsUserMenuOpen(false);
      }
    };

    const handleEscape = (event) => {
      if (event.key === 'Escape') {
        setIsUserMenuOpen(false);
        setIsMobileNavOpen(false);
      }
    };

    const handleMobileNavClick = (event) => {
      if (!headerRef.current) {
        return;
      }

      if (!headerRef.current.contains(event.target)) {
        setIsMobileNavOpen(false);
      }
    };

    document.addEventListener('mousedown', handleDocumentClick);
    document.addEventListener('mousedown', handleMobileNavClick);
    document.addEventListener('keydown', handleEscape);

    return () => {
      document.removeEventListener('mousedown', handleDocumentClick);
      document.removeEventListener('mousedown', handleMobileNavClick);
      document.removeEventListener('keydown', handleEscape);
    };
  }, []);

  useEffect(() => {
    setIsUserMenuOpen(false);
    setIsMobileNavOpen(false);
  }, [pathname]);

  useEffect(() => {
    const loadNotifications = async () => {
      try {
        const response = await api('/notifications');
        const items = Array.isArray(response.items) ? response.items : [];
        const unread = items.reduce((total, item) => total + Number(item.unread_count || 0), 0);
        setNotificationCount(unread);
      } catch (error) {
        setNotificationCount(0);
      }
    };

    loadNotifications();
  }, [pathname]);

  return (
    <div className="content-shell">
      <div className="top-strip-actions">
        <button type="button" className="top-strip-icon" onClick={() => navigate('/notifications')} title="Notificaciones" aria-label="Notificaciones">
          <svg viewBox="0 0 24 24" focusable="false">
            <path d="M12 2a6 6 0 0 0-6 6v3.35L4.26 14.25A1 1 0 0 0 5.12 16h13.76a1 1 0 0 0 .86-1.75L18 11.35V8a6 6 0 0 0-6-6zm0 20a3 3 0 0 0 2.82-2H9.18A3 3 0 0 0 12 22z" />
          </svg>
        </button>
        <div className="user-menu user-menu-top" ref={userMenuRef}>
          <button
            type="button"
            className="top-strip-icon top-strip-icon-avatar"
            title="Abrir menu de usuario"
            aria-label="Abrir menu de usuario"
            aria-haspopup="menu"
            aria-expanded={isUserMenuOpen}
            onClick={() => setIsUserMenuOpen((prev) => !prev)}
          >
            {session.user.profilePhoto ? (
              <img src={session.user.profilePhoto} alt="Foto de perfil" className="top-strip-avatar-img" />
            ) : (
              <svg viewBox="0 0 24 24" focusable="false">
                <path d="M12 12a4 4 0 1 0-4-4 4 4 0 0 0 4 4zm0 2c-3.33 0-6 1.8-6 4v2h12v-2c0-2.2-2.67-4-6-4z" />
              </svg>
            )}
          </button>

          {isUserMenuOpen ? (
            <div className="user-menu-dropdown user-menu-dropdown-top" role="menu">
              <div className="user-menu-meta">
                <strong>{session.user.name}</strong>
                <small>{session.user.specialty || session.user.role}</small>
              </div>
              {canAccessDashboard ? (
                <button type="button" className="user-menu-item" onClick={() => navigate('/dashboard')}>
                  Panel de dashboard
                </button>
              ) : null}
              <button type="button" className="user-menu-item" onClick={() => navigate('/profile')}>
                Mi perfil
              </button>
              {notificationCount > 0 ? (
                <button type="button" className="user-menu-item user-menu-item-row" onClick={() => navigate('/notifications')}>
                  <span className="user-menu-item-icon" aria-hidden="true">
                    <svg viewBox="0 0 24 24" focusable="false">
                      <path d="M12 2a6 6 0 0 0-6 6v3.35l-1.74 2.9A1 1 0 0 0 5.12 16h13.76a1 1 0 0 0 .86-1.75L18 11.35V8a6 6 0 0 0-6-6zm0 20a3 3 0 0 0 2.82-2H9.18A3 3 0 0 0 12 22z" />
                    </svg>
                  </span>
                  <span>Notificaciones</span>
                  <span className="user-menu-badge" aria-label={`${notificationCount} notificaciones`}>
                    {notificationCount}
                  </span>
                </button>
              ) : null}
              <button type="button" className="user-menu-item user-menu-item-danger" onClick={onLogout}>
                Cerrar sesion
              </button>
            </div>
          ) : null}
        </div>
        <button type="button" className="top-strip-icon" onClick={onLogout} title="Salir" aria-label="Salir">
          <svg viewBox="0 0 24 24" focusable="false">
            <path d="M10 17l5-5-5-5v3H3v4h7v3zm11-14h-8v2h8v14h-8v2h8a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" />
          </svg>
        </button>
      </div>

      <header className="app-header" ref={headerRef}>
        <div className="header-brand">
          <div className="brand-mark">
            <img src="/logo/logo-sin-fondo.webp" alt="AutismoCeuta" className="brand-logo" />
          </div>
          <div>
            <p className="eyebrow">Asociacion</p>
            <h2>AutismoCeuta</h2>
          </div>
        </div>

        <button
          type="button"
          className="mobile-nav-toggle"
          aria-label="Abrir menu"
          aria-haspopup="true"
          aria-expanded={isMobileNavOpen}
          onClick={() => setIsMobileNavOpen((prev) => !prev)}
        >
          <svg viewBox="0 0 24 24" focusable="false">
            <path d="M3 6h18M3 12h18M3 18h18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
          </svg>
        </button>

        <div className="sidebar-user-card">
          <span className="sidebar-user-avatar" aria-hidden="true">
            {session.user.profilePhoto ? (
              <img src={session.user.profilePhoto} alt="" className="sidebar-user-avatar-img" />
            ) : (
              <svg viewBox="0 0 24 24" focusable="false">
                <path d="M12 12a4 4 0 1 0-4-4 4 4 0 0 0 4 4zm0 2c-3.33 0-6 1.8-6 4v2h12v-2c0-2.2-2.67-4-6-4z" />
              </svg>
            )}
          </span>
          <span className="sidebar-user-meta">
            <strong>{session.user.name}</strong>
            <small>{session.user.role}</small>
          </span>
        </div>

        <nav className={`header-nav ${isMobileNavOpen ? 'is-open' : ''}`}>
          {adminMainLinks.length > 0 ? (
            <div className="header-nav-section">
              {adminMainLinks.map((item) => renderNavLink(item))}
            </div>
          ) : null}

          {panelAdminLinks.length > 0 ? (
            <div className="header-nav-section">
              <p className="header-nav-title">Panel Admin</p>
              {panelAdminLinks.map((item) => renderNavLink(item))}
            </div>
          ) : null}

          {therapistPanelLinks.length > 0 ? (
            <div className="header-nav-section">
              <p className="header-nav-title">Panel Terapeuta</p>
              {therapistPanelLinks.map((item) => renderNavLink(item))}
            </div>
          ) : null}

          <div className="header-nav-section header-nav-section-bottom">
            {utilityLinks.map((item) => {
              const badge = item.to === '/notifications' && notificationCount > 0
                ? <span className="header-link-badge">{notificationCount}</span>
                : null;
              return renderNavLink(item, badge);
            })}
          </div>
        </nav>

      </header>
    </div>
  );
}

function LoginPage() {
  const { refreshSession, notify } = useApp();
  const { navigate } = useRouter();
  const [form, setForm] = useState({ username: '', password: '' });
  const [loading, setLoading] = useState(false);

  const submit = async (event) => {
    event.preventDefault();
    setLoading(true);

    try {
      const data = await api('/auth/login', {
        method: 'POST',
        body: form,
      });
      await refreshSession();
      navigate(data.homePath || '/dashboard', { replace: true });
    } catch (error) {
      notify('error', error.message || 'No se pudo iniciar sesion.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <section className="auth-card">
      <div className="auth-card-brand">
        <div className="brand-mark">
          <img src="/logo/logo-sin-fondo.webp" alt="AutismoCeuta" className="brand-logo" />
        </div>
        <div>
          <p className="eyebrow">Acceso seguro</p>
          <h1>AutismoCeuta</h1>
          <p>Inicia sesion para entrar al panel.</p>
        </div>
      </div>
      <form className="stack" onSubmit={submit}>
        <label>
          Usuario
          <input
            value={form.username}
            onChange={(event) => setForm((prev) => ({ ...prev, username: event.target.value }))}
            required
          />
        </label>
        <label>
          Contrasena
          <input
            type="password"
            value={form.password}
            onChange={(event) => setForm((prev) => ({ ...prev, password: event.target.value }))}
            required
          />
        </label>
        <button className="button button-primary" type="submit" disabled={loading}>
          {loading ? 'Entrando...' : 'Entrar'}
        </button>
      </form>
    </section>
  );
}

function DashboardPage() {
  const { notify, refreshSession, session } = useApp();
  const [loading, setLoading] = useState(true);
  const [data, setData] = useState({ stats: {}, recentDocuments: [], moduleControls: [], moduleSettings: {}, orgSettings: {} });
  const [isBackingUp, setIsBackingUp] = useState(false);
  const [isBackingUpFull, setIsBackingUpFull] = useState(false);
  const [isRestoreModalOpen, setIsRestoreModalOpen] = useState(false);
  const [restoreFile, setRestoreFile] = useState(null);
  const [restoreConfirmText, setRestoreConfirmText] = useState('');
  const [isRestoring, setIsRestoring] = useState(false);
  const [orgForm, setOrgForm] = useState({
    legalName: '',
    taxId: '',
    defaultTaxName: 'IVA',
    defaultTaxRate: '21.00',
    invoicePrefix: 'FAC-',
  });
  const [isSavingOrg, setIsSavingOrg] = useState(false);

  const load = useCallback(async () => {
    setLoading(true);
    try {
      const response = await api('/dashboard');
      setData(response);
      setOrgForm((prev) => ({ ...prev, ...(response.orgSettings || {}) }));
      await refreshSession();
    } catch (error) {
      notify('error', error.message || 'No se pudo cargar el panel.');
    } finally {
      setLoading(false);
    }
  }, [notify, refreshSession]);

  const saveOrgSettings = async (event) => {
    event.preventDefault();
    setIsSavingOrg(true);
    try {
      const response = await api('/settings/org', { method: 'PUT', body: orgForm });
      setData((prev) => ({ ...prev, orgSettings: response.orgSettings }));
      setOrgForm((prev) => ({ ...prev, ...response.orgSettings }));
      await refreshSession();
      notify('success', 'Datos de la organizacion actualizados.');
    } catch (error) {
      notify('error', error.message || 'No se pudieron guardar los datos de la organizacion.');
    } finally {
      setIsSavingOrg(false);
    }
  };

  useEffect(() => {
    load();
  }, [load]);

  const toggleModule = async (moduleKey) => {
    try {
      const response = await api(`/dashboard/modules/${moduleKey}/toggle`, { method: 'POST' });
      setData((prev) => ({ ...prev, moduleSettings: response.moduleSettings }));
      await refreshSession();
      notify('success', 'Modulo actualizado.');
    } catch (error) {
      notify('error', error.message || 'No se pudo cambiar el modulo.');
    }
  };

  const downloadBackup = async () => {
    setIsBackingUp(true);
    try {
      const response = await fetch('/api/backup', { credentials: 'include' });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw new Error(errorData.message || 'No se pudo generar la copia de seguridad.');
      }

      const blob = await response.blob();
      const disposition = response.headers.get('Content-Disposition') || '';
      const filenameMatch = /filename="([^"]+)"/.exec(disposition);
      const url = URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      link.download = filenameMatch ? filenameMatch[1] : 'backup-autismoceuta.zip';
      document.body.appendChild(link);
      link.click();
      link.remove();
      URL.revokeObjectURL(url);
      notify('success', 'Copia de seguridad descargada.');
    } catch (error) {
      notify('error', error.message || 'No se pudo generar la copia de seguridad.');
    } finally {
      setIsBackingUp(false);
    }
  };

  const downloadFullBackup = async () => {
    setIsBackingUpFull(true);
    try {
      const response = await fetch('/api/backup/full', { credentials: 'include' });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw new Error(errorData.message || 'No se pudo generar la copia completa del sitio.');
      }

      const blob = await response.blob();
      const disposition = response.headers.get('Content-Disposition') || '';
      const filenameMatch = /filename="([^"]+)"/.exec(disposition);
      const url = URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      link.download = filenameMatch ? filenameMatch[1] : 'sitio-completo-autismoceuta.zip';
      document.body.appendChild(link);
      link.click();
      link.remove();
      URL.revokeObjectURL(url);
      notify('success', 'Copia completa del sitio descargada.');
    } catch (error) {
      notify('error', error.message || 'No se pudo generar la copia completa del sitio.');
    } finally {
      setIsBackingUpFull(false);
    }
  };

  const openRestoreModal = () => {
    setRestoreFile(null);
    setRestoreConfirmText('');
    setIsRestoreModalOpen(true);
  };

  const closeRestoreModal = () => {
    if (isRestoring) {
      return;
    }
    setIsRestoreModalOpen(false);
  };

  const restoreBackup = async () => {
    if (!restoreFile) {
      notify('error', 'Selecciona el archivo ZIP de la copia de seguridad.');
      return;
    }

    if (restoreConfirmText !== 'RESTAURAR') {
      notify('error', 'Escribe RESTAURAR para confirmar.');
      return;
    }

    setIsRestoring(true);
    try {
      const formData = new FormData();
      formData.append('backup', restoreFile);
      formData.append('confirm', restoreConfirmText);

      const response = await fetch('/api/backup/restore', {
        method: 'POST',
        credentials: 'include',
        body: formData,
      });
      const responseData = await response.json().catch(() => ({}));

      if (!response.ok) {
        throw new Error(responseData.message || 'No se pudo restaurar la copia de seguridad.');
      }

      notify('success', responseData.message || 'Copia de seguridad restaurada correctamente.');
      setIsRestoreModalOpen(false);
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo restaurar la copia de seguridad.');
    } finally {
      setIsRestoring(false);
    }
  };

  if (loading) {
    return <section className="panel-card">Cargando panel...</section>;
  }

  return (
    <>
      <section className="list-card">
        <h3>Activacion de modulos</h3>
        <p>Activa o desactiva cada modulo del sistema desde este panel.</p>
        <div className="module-grid">
          {data.moduleControls.map((module) => {
            const enabled = Boolean(data.moduleSettings[module.key]);

            return (
              <article key={module.key} className="module-card">
                <div>
                  <strong>{module.label}</strong>
                  <p>{module.description}</p>
                </div>
                <button
                  type="button"
                  className={`module-switch ${enabled ? 'is-on' : 'is-off'}`}
                  aria-label={`Cambiar estado de ${module.label}`}
                  onClick={() => toggleModule(module.key)}
                >
                  <span className="module-switch-body" aria-hidden="true">
                    <span className="module-switch-track">
                      <span className="on">On</span>
                      <span className="off">Off</span>
                    </span>
                    <i className="module-switch-thumb" />
                  </span>
                </button>
              </article>
            );
          })}
        </div>
      </section>

      {session.user.role === 'root' ? (
        <section className="list-card">
          <h3>Datos de la organizacion</h3>
          <p>Estos datos se usan para generar facturas y aparecen en el resto de la aplicacion.</p>
          <form className="form-grid" onSubmit={saveOrgSettings}>
            <label>
              Nombre legal
              <input
                type="text"
                value={orgForm.legalName}
                onChange={(event) => setOrgForm((prev) => ({ ...prev, legalName: event.target.value }))}
                required
              />
            </label>

            <label>
              CIF / NIF
              <input
                type="text"
                value={orgForm.taxId}
                onChange={(event) => setOrgForm((prev) => ({ ...prev, taxId: event.target.value }))}
              />
            </label>

            <label>
              Impuesto por defecto
              <TaxTypeSelect
                taxName={orgForm.defaultTaxName}
                taxRate={orgForm.defaultTaxRate}
                onSelect={(nextTaxName, nextTaxRate) =>
                  setOrgForm((prev) => ({ ...prev, defaultTaxName: nextTaxName, defaultTaxRate: nextTaxRate }))
                }
              />
            </label>

            <label>
              Prefijo de factura
              <input
                type="text"
                value={orgForm.invoicePrefix}
                onChange={(event) => setOrgForm((prev) => ({ ...prev, invoicePrefix: event.target.value }))}
                placeholder="FAC-"
              />
            </label>

            <button type="submit" className="button button-primary" disabled={isSavingOrg}>
              {isSavingOrg ? 'Guardando...' : 'Guardar datos de la organizacion'}
            </button>
          </form>
        </section>
      ) : null}

      <div className={session.user.role === 'root' ? 'two-card-row' : ''}>
        <section className="list-card">
          <h3>Copia de seguridad</h3>
          <p>Descarga un archivo ZIP con todos los datos del sistema, organizados por carpeta de niño (datos, fichas clinicas, sesiones y facturacion) junto con todos los archivos adjuntos.</p>
          <button type="button" className="button button-primary" onClick={downloadBackup} disabled={isBackingUp}>
            {isBackingUp ? 'Generando copia...' : 'Descargar copia de seguridad'}
          </button>
        </section>

        {session.user.role === 'root' ? (
          <section className="list-card">
            <h3>Restaurar copia de seguridad</h3>
            <p>
              Sube un archivo ZIP generado previamente para restaurar los datos. Esta accion
              <strong> reemplaza por completo</strong> los niños, fichas clinicas, sesiones, facturacion, terapias,
              asignaciones y contabilidad actuales por los del archivo. Las cuentas de usuario y sus contraseñas no se ven afectadas.
            </p>
            <button type="button" className="button button-danger" onClick={openRestoreModal}>
              Restaurar copia de seguridad
            </button>
          </section>
        ) : null}

        {session.user.role === 'root' ? (
          <section className="list-card">
            <h3>Copia completa del sitio</h3>
            <p>
              Descarga un ZIP con el codigo fuente completo de la web (carpeta "sitio/") mas una copia exacta de la
              base de datos, util para migrar o restaurar la aplicacion entera en otro servidor. No se incluyen
              node_modules; se reinstalan con "npm install".
            </p>
            <button type="button" className="button button-primary" onClick={downloadFullBackup} disabled={isBackingUpFull}>
              {isBackingUpFull ? 'Generando copia...' : 'Descargar copia completa del sitio'}
            </button>
          </section>
        ) : null}
      </div>

      {isRestoreModalOpen ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label="Restaurar copia de seguridad">
          <div className="modal-card">
            <div className="modal-head">
              <h3>Restaurar copia de seguridad</h3>
              <button type="button" className="modal-close" onClick={closeRestoreModal} aria-label="Cerrar">
                X
              </button>
            </div>

            <p>
              Esta accion sustituira todos los niños, fichas clinicas, sesiones, facturacion, terapias, asignaciones y
              contabilidad actuales por los datos del archivo ZIP. No se puede deshacer.
            </p>

            <div className="form-field">
              <label htmlFor="restore-file">Archivo ZIP de la copia de seguridad</label>
              <input
                id="restore-file"
                type="file"
                accept=".zip"
                disabled={isRestoring}
                onChange={(event) => setRestoreFile(event.target.files[0] || null)}
              />
            </div>

            <div className="form-field">
              <label htmlFor="restore-confirm">Escribe RESTAURAR para confirmar</label>
              <input
                id="restore-confirm"
                type="text"
                value={restoreConfirmText}
                disabled={isRestoring}
                onChange={(event) => setRestoreConfirmText(event.target.value)}
                placeholder="RESTAURAR"
              />
            </div>

            <div className="inline-actions session-modal-actions">
              <button type="button" className="button button-ghost" onClick={closeRestoreModal} disabled={isRestoring}>
                Cancelar
              </button>
              <button
                type="button"
                className="button button-danger"
                onClick={restoreBackup}
                disabled={isRestoring || !restoreFile || restoreConfirmText !== 'RESTAURAR'}
              >
                {isRestoring ? 'Restaurando...' : 'Restaurar'}
              </button>
            </div>
          </div>
        </div>
      ) : null}

      <section className="stats-grid">
        <article className="stat-card accent-blue">
          <span>Niños</span>
          <strong>{data.stats.patientCount || 0}</strong>
          <small>Fichas activas registradas</small>
        </article>
        <article className="stat-card accent-red">
          <span>Documentos</span>
          <strong>{data.stats.documentCount || 0}</strong>
          <small>Informes y seguimientos</small>
        </article>
        <article className="stat-card accent-sky">
          <span>Terapias</span>
          <strong>{data.stats.therapyCount || 0}</strong>
          <small>Especialidades disponibles</small>
        </article>
        <article className="stat-card accent-gold">
          <span>Terapeutas</span>
          <strong>{data.stats.therapistCount || 0}</strong>
          <small>Usuarios asistenciales</small>
        </article>
      </section>

      <section className="list-card">
        <h3>Actividad reciente</h3>
        <div className="list-stack">
          {data.recentDocuments.length === 0 ? <p>Sin actividad reciente.</p> : null}
          {data.recentDocuments.map((item) => (
            <article key={item.id} className="inline-card">
              <strong>{item.title}</strong>
              <p>{item.patient_name}</p>
              <small>{formatDate(item.updated_at)}</small>
            </article>
          ))}
        </div>
      </section>
    </>
  );
}

function PatientsPage() {
  const { notify } = useApp();
  const [patients, setPatients] = useState([]);
  const [form, setForm] = useState(emptyPatient());
  const [editingId, setEditingId] = useState(0);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [patientToDelete, setPatientToDelete] = useState(null);

  const load = useCallback(async () => {
    try {
      const response = await api('/patients');
      setPatients(response.patients || []);
    } catch (error) {
      notify('error', error.message || 'No se pudieron cargar los niños.');
    }
  }, [notify]);

  useEffect(() => {
    load();
  }, [load]);

  const reset = () => {
    setEditingId(0);
    setForm(emptyPatient());
  };

  const openCreateModal = () => {
    reset();
    setIsModalOpen(true);
  };

  const openEditModal = (patient) => {
    setEditingId(patient.id);
    setForm({
      first_name: patient.first_name || '',
      last_name: patient.last_name || '',
      birth_date: patient.birth_date || '',
      diagnosis: patient.diagnosis || '',
      allergies: patient.allergies || '',
      school: patient.school || '',
      guardian_name: patient.guardian_name || '',
      guardian_phone: patient.guardian_phone || '',
      assigned_therapist_name: patient.assigned_therapist_name || '',
      guardian_email: patient.guardian_email || '',
      address: patient.address || '',
      observations: patient.observations || '',
    });
    setIsModalOpen(true);
  };

  const closeModal = () => {
    setIsModalOpen(false);
    reset();
  };

  const submit = async (event) => {
    event.preventDefault();

    try {
      if (editingId) {
        await api(`/patients/${editingId}`, { method: 'PUT', body: form });
        notify('success', 'Ficha actualizada.');
      } else {
        await api('/patients', { method: 'POST', body: form });
        notify('success', 'Niño creado correctamente.');
      }
      closeModal();
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo guardar el niño.');
    }
  };

  const remove = async () => {
    if (!patientToDelete) {
      return;
    }

    try {
      await api(`/patients/${patientToDelete.id}`, { method: 'DELETE' });
      notify('success', 'Niño eliminado.');
      setPatientToDelete(null);
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar.');
    }
  };

  return (
    <section className="list-card">
      <div className="section-head">
        <h2>Niños</h2>
        <button type="button" className="button button-primary" onClick={openCreateModal}>
          Nuevo niño
        </button>
      </div>
      <SimpleTable
        columns={['Nombre', 'Tutor', 'Telefono tutor', 'Actualizado', 'Acciones']}
        rows={patients.map((patient) => [
          `${patient.first_name} ${patient.last_name}`,
          patient.guardian_name || '-',
          patient.guardian_phone || '-',
          formatDate(patient.updated_at),
          <div className="action-row" key={`actions-${patient.id}`}>
            <IconActionButton icon="edit" label="Editar" onClick={() => openEditModal(patient)} />
            <IconActionButton icon="delete" label="Eliminar" tone="danger" onClick={() => setPatientToDelete(patient)} />
          </div>,
        ])}
      />

      {isModalOpen ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={editingId ? 'Editar niño' : 'Nuevo niño'}>
          <div className="modal-card">
            <div className="modal-head">
              <h3>{editingId ? 'Editar niño' : 'Nuevo niño'}</h3>
              <button type="button" className="modal-close" onClick={closeModal} aria-label="Cerrar">
                X
              </button>
            </div>
            <FormPatient form={form} setForm={setForm} onSubmit={submit} editing={Boolean(editingId)} onCancel={closeModal} />
          </div>
        </div>
      ) : null}

      <ConfirmActionModal
        open={Boolean(patientToDelete)}
        title="Eliminar niño"
        message={patientToDelete ? `Vas a eliminar a ${patientToDelete.first_name} ${patientToDelete.last_name}.` : ''}
        onCancel={() => setPatientToDelete(null)}
        onConfirm={remove}
        confirmLabel="Eliminar"
      />
    </section>
  );
}

function FormPatient({ form, setForm, onSubmit, editing, onCancel }) {
  const birthDateInputRef = useRef(null);
  const birthDatePickerRef = useRef(null);

  useEffect(() => {
    const input = birthDateInputRef.current;
    if (!input || !window.flatpickr) {
      return undefined;
    }

    const picker = window.flatpickr(input, {
      locale: (window.flatpickr.l10ns && window.flatpickr.l10ns.es) || 'default',
      dateFormat: 'Y-m-d',
      altInput: true,
      altFormat: 'd/m/Y',
      defaultDate: form.birth_date || null,
      allowInput: false,
      disableMobile: true,
      onChange: (selectedDates) => {
        if (!selectedDates.length) {
          setForm((p) => ({ ...p, birth_date: '' }));
          return;
        }

        const selectedDate = selectedDates[0];
        const normalized = `${selectedDate.getFullYear()}-${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}`;
        setForm((p) => ({ ...p, birth_date: normalized }));
      },
    });

    birthDatePickerRef.current = picker;

    return () => {
      picker.destroy();
      birthDatePickerRef.current = null;
    };
  }, []);

  const openBirthDatePicker = () => {
    const picker = birthDatePickerRef.current;
    if (picker) {
      picker.open();
    }
  };

  return (
    <form className="stack grid-2" onSubmit={onSubmit}>
      <label>
        Nombre
        <input value={form.first_name} onChange={(e) => setForm((p) => ({ ...p, first_name: e.target.value }))} required />
      </label>
      <label>
        Apellidos
        <input value={form.last_name} onChange={(e) => setForm((p) => ({ ...p, last_name: e.target.value }))} required />
      </label>
      <label>
        Fecha nacimiento
        <div className="localized-date-field">
          <input ref={birthDateInputRef} type="text" placeholder="DD/MM/AAAA" />
          <button type="button" className="localized-date-trigger" aria-label="Abrir calendario" onClick={openBirthDatePicker}>
            <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
              <path d="M7 2h2v2h6V2h2v2h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h3V2zm13 8H4v10h16V10zM4 8h16V6H4v2z" />
            </svg>
          </button>
        </div>
      </label>
      <label>
        Tutor
        <input value={form.guardian_name} onChange={(e) => setForm((p) => ({ ...p, guardian_name: e.target.value }))} />
      </label>
      <label>
        Telefono tutor
        <input value={form.guardian_phone} onChange={(e) => setForm((p) => ({ ...p, guardian_phone: e.target.value }))} />
      </label>
      <label>
        Terapeuta asignado
        <input value={form.assigned_therapist_name} readOnly placeholder="Sin asignar" />
      </label>
      <label className="span-2">
        Diagnostico
        <textarea
          className="patient-diagnosis-field"
          value={form.diagnosis}
          onChange={(e) => setForm((p) => ({ ...p, diagnosis: e.target.value }))}
        />
      </label>
      <div className="inline-actions span-2">
        <button className="button button-primary" type="submit">
          {editing ? 'Guardar cambios' : 'Crear niño'}
        </button>
        {editing ? (
          <button className="button button-ghost" type="button" onClick={onCancel}>
            Cancelar
          </button>
        ) : null}
      </div>
    </form>
  );
}

function DocumentsPage() {
  const { notify, session } = useApp();
  const [documents, setDocuments] = useState([]);
  const [completedSessions, setCompletedSessions] = useState([]);
  const [meta, setMeta] = useState({ patients: [], therapists: [], therapies: [] });
  const [selectedPatientId, setSelectedPatientId] = useState(0);
  const [selectedYear, setSelectedYear] = useState('all');
  const [editingId, setEditingId] = useState(0);
  const [form, setForm] = useState(emptyDocument());
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [previewDocument, setPreviewDocument] = useState(null);
  const [previewAttachments, setPreviewAttachments] = useState([]);
  const [previewSessionInfo, setPreviewSessionInfo] = useState(null);
  const [viewingAttachment, setViewingAttachment] = useState(null);
  const [documentToDelete, setDocumentToDelete] = useState(null);
  const [savedAttachments, setSavedAttachments] = useState([]);
  const documentAttachmentInputRef = useRef(null);
  const sessionDateInputRef = useRef(null);
  const sessionDatePickerRef = useRef(null);

  const getDocumentYear = useCallback((doc) => {
    const rawDate = doc.session_date || doc.updated_at || doc.created_at;
    if (!rawDate) {
      return 'Sin ano';
    }

    const parsed = new Date(rawDate);
    if (Number.isNaN(parsed.getTime())) {
      return 'Sin ano';
    }

    return String(parsed.getFullYear());
  }, []);

  const getSessionYear = useCallback((sessionItem) => {
    if (!sessionItem.session_date) {
      return 'Sin ano';
    }

    const parsed = new Date(sessionItem.session_date);
    if (Number.isNaN(parsed.getTime())) {
      return 'Sin ano';
    }

    return String(parsed.getFullYear());
  }, []);

  const yearOptions = useMemo(() => {
    const years = Array.from(new Set(documents.map((doc) => getDocumentYear(doc))));
    return years.sort((a, b) => {
      if (a === 'Sin ano') {
        return 1;
      }
      if (b === 'Sin ano') {
        return -1;
      }
      return Number(b) - Number(a);
    });
  }, [documents, getDocumentYear]);

  const visibleDocuments = useMemo(() => {
    if (selectedYear === 'all') {
      return documents;
    }
    return documents.filter((doc) => getDocumentYear(doc) === selectedYear);
  }, [documents, selectedYear, getDocumentYear]);

  const getCompletedSessionStatus = useCallback((sessionItem) => {
    if (sessionItem.completion_result === 'problem') {
      return { label: 'Sesión con incidencia', className: 'document-type-pill-danger' };
    }
    return { label: 'Sesión completada', className: 'document-type-pill-success' };
  }, []);

  const groupedDocuments = useMemo(() => {
    const groups = new Map();

    visibleDocuments.forEach((doc) => {
      const year = getDocumentYear(doc);
      if (!groups.has(year)) {
        groups.set(year, []);
      }
      groups.get(year).push(doc);
    });

    return Array.from(groups.entries()).sort(([yearA], [yearB]) => {
      if (yearA === 'Sin ano') {
        return 1;
      }
      if (yearB === 'Sin ano') {
        return -1;
      }
      return Number(yearB) - Number(yearA);
    });
  }, [visibleDocuments, getDocumentYear]);

  const load = useCallback(async () => {
    try {
      const [docs, metadata] = await Promise.all([
        api(`/documents${selectedPatientId ? `?patientId=${selectedPatientId}` : ''}`),
        api('/documents/meta'),
      ]);
      setDocuments(docs.documents || []);
      setMeta(metadata);
    } catch (error) {
      notify('error', error.message || 'No se pudieron cargar las fichas.');
    }

    try {
      const sessionsResponse = await api('/sessions');
      setCompletedSessions((sessionsResponse.items || []).filter((sessionItem) => sessionItem.is_completed));
    } catch (error) {
      setCompletedSessions([]);
    }
  }, [selectedPatientId, notify]);

  useEffect(() => {
    load();
  }, [load]);

  useEffect(() => {
    if (selectedYear === 'all') {
      return;
    }

    if (!yearOptions.includes(selectedYear)) {
      setSelectedYear('all');
    }
  }, [yearOptions, selectedYear]);

  useEffect(() => {
    if (!isModalOpen) {
      return undefined;
    }

    const input = sessionDateInputRef.current;
    if (!input || !window.flatpickr) {
      return undefined;
    }

    const picker = window.flatpickr(input, {
      locale: (window.flatpickr.l10ns && window.flatpickr.l10ns.es) || 'default',
      dateFormat: 'Y-m-d',
      altInput: true,
      altFormat: 'd/m/Y',
      defaultDate: form.session_date || null,
      allowInput: false,
      disableMobile: true,
      onChange: (selectedDates) => {
        if (!selectedDates.length) {
          setForm((p) => ({ ...p, session_date: '' }));
          return;
        }

        const selectedDate = selectedDates[0];
        const normalized = `${selectedDate.getFullYear()}-${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}`;
        setForm((p) => ({ ...p, session_date: normalized }));
      },
    });

    sessionDatePickerRef.current = picker;

    return () => {
      picker.destroy();
      sessionDatePickerRef.current = null;
    };
  }, [isModalOpen, editingId]);

  const openSessionDatePicker = () => {
    const picker = sessionDatePickerRef.current;
    if (picker) {
      picker.open();
    }
  };

  const defaultDocumentForm = useCallback(() => {
    const base = emptyDocument();
    if (session.user.role === 'terapeuta') {
      return { ...base, therapist_id: session.user.id };
    }
    return base;
  }, [session.user.id, session.user.role]);

  const openCreateModal = () => {
    setEditingId(0);
    setForm(defaultDocumentForm());
    setSavedAttachments([]);
    setIsModalOpen(true);
  };

  const closeModal = () => {
    setIsModalOpen(false);
    setEditingId(0);
    setForm(defaultDocumentForm());
    setSavedAttachments([]);
  };

  const closePreviewModal = () => {
    setPreviewDocument(null);
    setPreviewAttachments([]);
  };

  const openPreview = async (doc) => {
    setPreviewDocument(doc);
    setPreviewAttachments([]);
    try {
      const response = await api(`/documents/${doc.id}/attachments`);
      setPreviewAttachments(response.attachments || []);
    } catch (error) {
      notify('error', error.message || 'No se pudieron cargar los adjuntos.');
    }
  };

  const openSessionInfoPreview = (sessionItem) => {
    setPreviewSessionInfo(sessionItem);
  };

  const closeSessionInfoPreview = () => {
    setPreviewSessionInfo(null);
  };

  const readFileAsDataUrl = (file) => new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(typeof reader.result === 'string' ? reader.result : '');
    reader.onerror = () => reject(reader.error);
    reader.readAsDataURL(file);
  });

  const onDocumentAttachmentSelected = async (event) => {
    const files = Array.from(event.target.files || []);

    if (!files.length) {
      return;
    }

    const validFiles = [];

    for (const file of files) {
      if (!/^(application\/pdf|image\/(png|jpe?g|webp|gif))$/i.test(file.type)) {
        notify('error', `Formato no permitido para "${file.name}". Usa PDF, PNG, JPG, WEBP o GIF.`);
        continue;
      }

      if (file.size > 10_000_000) {
        notify('error', `"${file.name}" es demasiado grande. Maximo aproximado: 10MB.`);
        continue;
      }

      validFiles.push(file);
    }

    if (!validFiles.length) {
      event.target.value = '';
      return;
    }

    try {
      const newAttachments = await Promise.all(
        validFiles.map(async (file) => ({
          name: file.name,
          type: file.type,
          data_url: await readFileAsDataUrl(file),
          allow_download: false,
        }))
      );
      setForm((prev) => ({
        ...prev,
        attachments: [...prev.attachments, ...newAttachments],
      }));
    } catch (error) {
      notify('error', 'No se pudieron leer uno o mas archivos.');
    }

    event.target.value = '';
  };

  const removePendingAttachment = (index) => {
    setForm((prev) => ({
      ...prev,
      attachments: prev.attachments.filter((_, i) => i !== index),
    }));
  };

  const removeSavedAttachment = async (attachmentId) => {
    try {
      await api(`/documents/attachments/${attachmentId}`, { method: 'DELETE' });
      setSavedAttachments((prev) => prev.filter((item) => item.id !== attachmentId));
      notify('success', 'Adjunto eliminado.');
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar el adjunto.');
    }
  };

  const updateSavedAttachmentPermission = async (attachmentId, allowDownload) => {
    try {
      await api(`/documents/attachments/${attachmentId}/permission`, {
        method: 'PUT',
        body: { allow_download: allowDownload },
      });
      setSavedAttachments((prev) =>
        prev.map((item) => (item.id === attachmentId ? { ...item, allow_download: allowDownload ? 1 : 0 } : item))
      );
    } catch (error) {
      notify('error', error.message || 'No se pudo actualizar el permiso de descarga.');
    }
  };

  const updatePendingAttachmentPermission = (index, allowDownload) => {
    setForm((prev) => ({
      ...prev,
      attachments: prev.attachments.map((item, i) => (i === index ? { ...item, allow_download: allowDownload } : item)),
    }));
  };

  const viewDocumentAttachment = (attachment) => {
    if (!attachment || !attachment.data_url) {
      return;
    }

    if (session.user.role === 'usuario') {
      api(`/documents/attachments/${attachment.id}/read`, { method: 'POST' }).catch(() => {});
    }

    setViewingAttachment(attachment);
  };

  const closeAttachmentViewer = () => {
    setViewingAttachment(null);
  };

  const downloadDocumentAttachment = (attachment) => {
    if (!attachment || !attachment.data_url) {
      return;
    }

    const link = document.createElement('a');
    link.href = attachment.data_url;
    link.download = attachment.name || 'adjunto';
    document.body.appendChild(link);
    link.click();
    link.remove();
  };

  const submit = async (event) => {
    event.preventDefault();

    try {
      if (editingId) {
        await api(`/documents/${editingId}`, { method: 'PUT', body: form });
        notify('success', 'Ficha actualizada.');
      } else {
        await api('/documents', { method: 'POST', body: form });
        notify('success', 'Ficha creada.');
      }
      closeModal();
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo guardar la ficha.');
    }
  };

  const startEdit = async (doc) => {
    setEditingId(doc.id);
    setForm({
      patient_id: doc.patient_id,
      therapist_id: doc.therapist_id,
      therapy_id: doc.therapy_id || '',
      title: doc.title || '',
      document_type: doc.document_type || '',
      session_date: doc.session_date || '',
      summary: doc.summary || '',
      content: doc.content || '',
      objectives: doc.objectives || '',
      achievements: doc.achievements || '',
      recommendations: doc.recommendations || '',
      visible_to_parents: Boolean(doc.visible_to_parents),
      attachments: [],
    });
    setSavedAttachments([]);
    setIsModalOpen(true);
    try {
      const response = await api(`/documents/${doc.id}/attachments`);
      setSavedAttachments(response.attachments || []);
    } catch (error) {
      notify('error', error.message || 'No se pudieron cargar los adjuntos.');
    }
  };

  const remove = async () => {
    if (!documentToDelete) {
      return;
    }

    try {
      await api(`/documents/${documentToDelete.id}`, { method: 'DELETE' });
      notify('success', 'Ficha eliminada.');
      setDocumentToDelete(null);
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar la ficha.');
    }
  };

  const getTypeTone = (documentType) => {
    const normalized = String(documentType || '').toLowerCase();

    if (normalized.includes('seguimiento')) {
      return 'followup';
    }

    if (normalized.includes('progreso')) {
      return 'progress';
    }

    if (normalized.includes('evaluacion')) {
      return 'evaluation';
    }

    return 'neutral';
  };

  const buildPreview = (doc) => {
    const summary = String(doc.summary || '').trim();
    const content = String(doc.content || '').trim();
    const source = summary || content;
    if (!source) {
      return 'Sin resumen disponible.';
    }
    if (source.length <= 96) {
      return source;
    }
    return `${source.slice(0, 96)}...`;
  };

  return (
    <section className="list-card documents-board">
      <div className="documents-header-row">
        <h2 className="documents-title">
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
            <path d="M6 2h8l4 4v16H6V2zm8 1.5V7h3.5M8 11h8v1.8H8V11zm0 3.5h8v1.8H8v-1.8z" />
          </svg>
          <span>Fichas Clinicas</span>
        </h2>
        <button type="button" className="button button-primary" onClick={openCreateModal}>
          Nueva ficha
        </button>
      </div>

      <div className="documents-filters">
        <label className="documents-filter">
          <span className="sr-only">Filtrar por niño</span>
          <select value={selectedPatientId} onChange={(e) => setSelectedPatientId(Number(e.target.value || 0))}>
            <option value={0}>Todos los niños</option>
            {meta.patients.map((patient) => (
              <option key={patient.id} value={patient.id}>
                {patient.first_name} {patient.last_name}
              </option>
            ))}
          </select>
        </label>
        <label className="documents-filter">
          <span className="sr-only">Filtrar por año</span>
          <select value={selectedYear} onChange={(e) => setSelectedYear(e.target.value)}>
            <option value="all">Todos los años</option>
            {yearOptions.map((year) => (
              <option key={year} value={year}>
                {year}
              </option>
            ))}
          </select>
        </label>
      </div>

      {groupedDocuments.length === 0 ? (
        <p className="documents-empty">No hay fichas para los filtros seleccionados.</p>
      ) : (
        groupedDocuments.map(([year, items]) => (
          <div key={year} className="documents-year-block">
            <h3 className="documents-year-title">
              <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                <path d="M7 2h2v2h6V2h2v2h3v18H4V4h3V2zm11 8H6v10h12V10z" />
              </svg>
              <span>{year}</span>
            </h3>

            <div className="documents-grid">
              {items.map((doc) => (
                <article
                  key={doc.id}
                  className="document-card"
                  role="button"
                  tabIndex={0}
                  onClick={() => openPreview(doc)}
                  onKeyDown={(event) => {
                    if (event.key === 'Enter' || event.key === ' ') {
                      event.preventDefault();
                      openPreview(doc);
                    }
                  }}
                >
                  <div className="document-card-top">
                    <span className={`document-type-pill document-type-pill-${getTypeTone(doc.document_type)}`}>
                      {doc.document_type || 'Ficha clinica'}
                    </span>
                  </div>

                  <h4>{doc.title || `${doc.document_type || 'Ficha'} - ${doc.patient_name || 'Sin niño'}`}</h4>
                  <p className="document-card-patient">{doc.patient_name || 'Sin niño asignado'}</p>
                  <p className="document-card-summary">{buildPreview(doc)}</p>

                  <div className="document-card-meta">
                    <span>{formatDate(doc.session_date || doc.updated_at || doc.created_at)}</span>
                    {Number(doc.attachment_count || 0) > 0 ? (
                      <span className="document-attachment-indicator" title={`${doc.attachment_count} adjunto(s)`}>
                        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                          <path d="M16.5 6.5v10a4.5 4.5 0 0 1-9 0v-11a3 3 0 0 1 6 0v10a1.5 1.5 0 0 1-3 0v-9H9v9a3 3 0 0 0 6 0v-10a4.5 4.5 0 0 0-9 0v11a6 6 0 0 0 12 0v-10h-1.5z" />
                        </svg>
                        <span>{doc.attachment_count}</span>
                      </span>
                    ) : null}
                    <span className={`document-visibility ${doc.visible_to_parents ? 'is-visible' : 'is-hidden'}`}>
                      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                        {doc.visible_to_parents ? (
                          <path d="M12 5c5.5 0 9.5 4.03 10.8 6.03a1.8 1.8 0 0 1 0 1.94C21.5 14.97 17.5 19 12 19S2.5 14.97 1.2 12.97a1.8 1.8 0 0 1 0-1.94C2.5 9.03 6.5 5 12 5zm0 3.2A3.8 3.8 0 1 0 15.8 12 3.8 3.8 0 0 0 12 8.2z" />
                        ) : (
                          <path d="m3.3 2 18.7 18.7-1.4 1.4-3.05-3.05A12.4 12.4 0 0 1 12 20c-5.5 0-9.5-4.03-10.8-6.03a1.8 1.8 0 0 1 0-1.94A17.6 17.6 0 0 1 7.02 6.1L1.9 1 3.3 2zm8.7 5.2a4.8 4.8 0 0 1 4.8 4.8c0 .78-.19 1.52-.53 2.16l-1.56-1.56a2.8 2.8 0 0 0-3.29-3.29L9.84 7.73a4.8 4.8 0 0 1 2.16-.53zm0-2.2c5.5 0 9.5 4.03 10.8 6.03a1.8 1.8 0 0 1 0 1.94 17.7 17.7 0 0 1-3.8 4.1l-1.44-1.44a15 15 0 0 0 2.5-2.66c-1.08-1.52-4.35-5-8.06-5-.82 0-1.62.08-2.38.23L7.7 6.25c1.37-.79 2.81-1.25 4.3-1.25z" />
                        )}
                      </svg>
                      <span>{doc.visible_to_parents ? 'Visible' : 'Oculta'}</span>
                    </span>
                  </div>

                  <div className="document-card-actions action-row">
                    <IconActionButton
                      icon="edit"
                      label="Editar"
                      onClick={(event) => {
                        event.stopPropagation();
                        startEdit(doc);
                      }}
                    />
                    <IconActionButton
                      icon="delete"
                      label="Eliminar"
                      tone="danger"
                      onClick={(event) => {
                        event.stopPropagation();
                        setDocumentToDelete(doc);
                      }}
                    />
                  </div>
                </article>
              ))}
            </div>
          </div>
        ))
      )}

      {previewSessionInfo ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label="Vista de sesión">
          <div className="modal-card document-preview-modal">
            <div className="modal-head">
              <h3>{previewSessionInfo.patient_name || 'Sesión'}</h3>
              <button type="button" className="modal-close" onClick={closeSessionInfoPreview} aria-label="Cerrar">
                X
              </button>
            </div>

            <div className="document-preview-meta-row">
              <span className={`document-type-pill ${getCompletedSessionStatus(previewSessionInfo).className}`}>
                {getCompletedSessionStatus(previewSessionInfo).label}
              </span>
              <span>{previewSessionInfo.therapy_name || 'Sesión'}</span>
              <span>{formatDate(previewSessionInfo.session_date)}</span>
              <span>{previewSessionInfo.therapist_name || 'Sin terapeuta asignado'}</span>
            </div>

            <div className="document-preview-body">
              <section>
                <h4>Notas</h4>
                <p>{previewSessionInfo.notes || 'Sin notas registradas.'}</p>
              </section>
            </div>
          </div>
        </div>
      ) : null}

      {previewDocument ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label="Vista de ficha clinica">
          <div className="modal-card modal-card-wide document-preview-modal">
            <div className="modal-head">
              <h3>{previewDocument.title || `${previewDocument.document_type || 'Ficha'} - ${previewDocument.patient_name || ''}`}</h3>
              <button type="button" className="modal-close" onClick={closePreviewModal} aria-label="Cerrar">
                X
              </button>
            </div>

            <div className="document-preview-meta-row">
              <span className={`document-type-pill document-type-pill-${getTypeTone(previewDocument.document_type)}`}>
                {previewDocument.document_type || 'Ficha clinica'}
              </span>
              <span>{previewDocument.patient_name || 'Sin niño asignado'}</span>
              <span>{formatDate(previewDocument.session_date || previewDocument.updated_at || previewDocument.created_at)}</span>
              <span className={`document-visibility ${previewDocument.visible_to_parents ? 'is-visible' : 'is-hidden'}`}>
                <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                  {previewDocument.visible_to_parents ? (
                    <path d="M12 5c5.5 0 9.5 4.03 10.8 6.03a1.8 1.8 0 0 1 0 1.94C21.5 14.97 17.5 19 12 19S2.5 14.97 1.2 12.97a1.8 1.8 0 0 1 0-1.94C2.5 9.03 6.5 5 12 5zm0 3.2A3.8 3.8 0 1 0 15.8 12 3.8 3.8 0 0 0 12 8.2z" />
                  ) : (
                    <path d="m3.3 2 18.7 18.7-1.4 1.4-3.05-3.05A12.4 12.4 0 0 1 12 20c-5.5 0-9.5-4.03-10.8-6.03a1.8 1.8 0 0 1 0-1.94A17.6 17.6 0 0 1 7.02 6.1L1.9 1 3.3 2zm8.7 5.2a4.8 4.8 0 0 1 4.8 4.8c0 .78-.19 1.52-.53 2.16l-1.56-1.56a2.8 2.8 0 0 0-3.29-3.29L9.84 7.73a4.8 4.8 0 0 1 2.16-.53zm0-2.2c5.5 0 9.5 4.03 10.8 6.03a1.8 1.8 0 0 1 0 1.94 17.7 17.7 0 0 1-3.8 4.1l-1.44-1.44a15 15 0 0 0 2.5-2.66c-1.08-1.52-4.35-5-8.06-5-.82 0-1.62.08-2.38.23L7.7 6.25c1.37-.79 2.81-1.25 4.3-1.25z" />
                  )}
                </svg>
                <span>{previewDocument.visible_to_parents ? 'Visible' : 'Oculta'}</span>
              </span>
            </div>

            <div className="document-preview-body">
              <section>
                <h4>Contenido</h4>
                <p>{previewDocument.content || previewDocument.summary || 'Sin contenido disponible.'}</p>
              </section>

              {previewDocument.objectives ? (
                <section>
                  <h4>Objetivos</h4>
                  <p>{previewDocument.objectives}</p>
                </section>
              ) : null}

              {previewDocument.achievements ? (
                <section>
                  <h4>Logros</h4>
                  <p>{previewDocument.achievements}</p>
                </section>
              ) : null}

              {previewDocument.recommendations ? (
                <section>
                  <h4>Recomendaciones</h4>
                  <p>{previewDocument.recommendations}</p>
                </section>
              ) : null}

              {previewAttachments.length > 0 ? (
                <section>
                  <h4>Adjuntos</h4>
                  <div className="attachment-card-grid">
                    {previewAttachments.map((attachment) => {
                      const canDownload = session.user.role !== 'usuario' || Number(attachment.allow_download) === 1;

                      return (
                        <div key={attachment.id} className="attachment-card attachment-card-preview">
                          <button
                            type="button"
                            className="attachment-card-body"
                            onClick={() => viewDocumentAttachment(attachment)}
                            title="Ver adjunto"
                          >
                            <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" className="attachment-card-icon">
                              <path d="M6 2h9l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2zm8 1.5V8h4.5L14 3.5z" />
                            </svg>
                            <span className="attachment-card-name">{attachment.name}</span>
                          </button>
                          {canDownload ? (
                            <div className="attachment-card-footer">
                              <IconActionButton
                                icon="download"
                                label="Descargar adjunto"
                                onClick={() => downloadDocumentAttachment(attachment)}
                              />
                              {session.user.role !== 'usuario' ? (
                                <span className={`attachment-read-status ${Number(attachment.read_count) > 0 ? 'is-read' : 'is-unread'}`}>
                                  {Number(attachment.read_count) > 0
                                    ? `Leído por el tutor (${attachment.read_count})`
                                    : 'No leído por el tutor'}
                                </span>
                              ) : null}
                            </div>
                          ) : null}
                        </div>
                      );
                    })}
                  </div>
                </section>
              ) : null}

              <section>
                <h4>Sesiones completadas</h4>
                {completedSessions.filter((sessionItem) => sessionItem.patient_id === previewDocument.patient_id).length === 0 ? (
                  <p className="document-card-summary">Este niño no tiene sesiones completadas todavía.</p>
                ) : (
                  <div className="session-history-grid">
                    {completedSessions
                      .filter((sessionItem) => sessionItem.patient_id === previewDocument.patient_id)
                      .map((sessionItem) => {
                        const status = getCompletedSessionStatus(sessionItem);
                        return (
                          <button
                            key={sessionItem.id}
                            type="button"
                            className="session-history-card"
                            onClick={() => openSessionInfoPreview(sessionItem)}
                          >
                            <span className={`document-type-pill ${status.className}`}>{status.label}</span>
                            <span className="session-history-item-therapy">{sessionItem.therapy_name || 'Sesión'}</span>
                            <span className="session-history-item-date">{formatDate(sessionItem.session_date)}</span>
                          </button>
                        );
                      })}
                  </div>
                )}
              </section>
            </div>
          </div>
        </div>
      ) : null}

      {previewSessionInfo ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label="Vista de sesión">
          <div className="modal-card document-preview-modal">
            <div className="modal-head">
              <h3>{previewSessionInfo.patient_name || 'Sesión'}</h3>
              <button type="button" className="modal-close" onClick={closeSessionInfoPreview} aria-label="Cerrar">
                X
              </button>
            </div>

            <div className="document-preview-meta-row">
              <span className={`document-type-pill ${getCompletedSessionStatus(previewSessionInfo).className}`}>
                {getCompletedSessionStatus(previewSessionInfo).label}
              </span>
              <span>{previewSessionInfo.therapy_name || 'Sesión'}</span>
              <span>{formatDate(previewSessionInfo.session_date)}</span>
              <span>{previewSessionInfo.therapist_name || 'Sin terapeuta asignado'}</span>
            </div>

            <div className="document-preview-body">
              <section>
                <h4>Notas</h4>
                <p>{previewSessionInfo.notes || 'Sin notas registradas.'}</p>
              </section>
            </div>
          </div>
        </div>
      ) : null}

      {viewingAttachment ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label="Vista de adjunto">
          <div className="modal-card modal-card-wide attachment-viewer-modal">
            <div className="modal-head">
              <h3>{viewingAttachment.name}</h3>
              <button type="button" className="modal-close" onClick={closeAttachmentViewer} aria-label="Cerrar">
                X
              </button>
            </div>

            <div className="attachment-viewer-body">
              {String(viewingAttachment.type || '').startsWith('image/') ? (
                <img src={viewingAttachment.data_url} alt={viewingAttachment.name} />
              ) : viewingAttachment.type === 'application/pdf' ? (
                <iframe src={viewingAttachment.data_url} title={viewingAttachment.name} />
              ) : (
                <p className="document-card-summary">
                  No hay vista previa disponible para este tipo de archivo.
                </p>
              )}
            </div>
          </div>
        </div>
      ) : null}

      {isModalOpen ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={editingId ? 'Editar ficha' : 'Nueva ficha'}>
          <div className="modal-card modal-card-wide">
            <div className="modal-head">
              <h3>{editingId ? 'Editar ficha clinica' : 'Nueva ficha clinica'}</h3>
              <button type="button" className="modal-close" onClick={closeModal} aria-label="Cerrar">
                X
              </button>
            </div>

            <div className="modal-scroll">
              <form className="stack grid-2" onSubmit={submit}>
                <label>
                  Niño
                  <select
                    value={form.patient_id}
                    onChange={(e) => setForm((p) => ({ ...p, patient_id: Number(e.target.value || 0) }))}
                    required
                  >
                    <option value="">Seleccionar</option>
                    {meta.patients.map((patient) => (
                      <option key={patient.id} value={patient.id}>
                        {patient.first_name} {patient.last_name}
                      </option>
                    ))}
                  </select>
                </label>
                <label>
                  Terapeuta
                  <select
                    value={form.therapist_id}
                    onChange={(e) => setForm((p) => ({ ...p, therapist_id: Number(e.target.value || 0) }))}
                    disabled={session.user.role === 'terapeuta'}
                    required
                  >
                    <option value="">Seleccionar</option>
                    {meta.therapists.map((therapist) => (
                      <option key={therapist.id} value={therapist.id}>
                        {therapist.name}
                      </option>
                    ))}
                  </select>
                </label>
                <label>
                  Tipo
                  <input
                    value={form.document_type}
                    onChange={(e) => setForm((p) => ({ ...p, document_type: e.target.value }))}
                    required
                  />
                </label>
                <label>
                  Titulo
                  <input value={form.title} onChange={(e) => setForm((p) => ({ ...p, title: e.target.value }))} required />
                </label>
                <label>
                  Fecha sesion
                  <div className="localized-date-field">
                    <input ref={sessionDateInputRef} type="text" placeholder="DD/MM/AAAA" />
                    <button type="button" className="localized-date-trigger" aria-label="Abrir calendario" onClick={openSessionDatePicker}>
                      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                        <path d="M7 2h2v2h6V2h2v2h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h3V2zm13 8H4v10h16V10zM4 8h16V6H4v2z" />
                      </svg>
                    </button>
                  </div>
                </label>
                <label>
                  Terapia
                  <select value={form.therapy_id} onChange={(e) => setForm((p) => ({ ...p, therapy_id: Number(e.target.value || 0) }))}>
                    <option value="">Sin terapia</option>
                    {meta.therapies.map((therapy) => (
                      <option key={therapy.id} value={therapy.id}>
                        {therapy.name}
                      </option>
                    ))}
                  </select>
                </label>
                <label className="span-2">
                  Resumen
                  <textarea value={form.summary} onChange={(e) => setForm((p) => ({ ...p, summary: e.target.value }))} />
                </label>
                <label className="span-2">
                  Contenido
                  <textarea value={form.content} onChange={(e) => setForm((p) => ({ ...p, content: e.target.value }))} required />
                </label>
                <label>
                  Objetivos
                  <textarea value={form.objectives} onChange={(e) => setForm((p) => ({ ...p, objectives: e.target.value }))} />
                </label>
                <label>
                  Logros
                  <textarea value={form.achievements} onChange={(e) => setForm((p) => ({ ...p, achievements: e.target.value }))} />
                </label>
                <label className="span-2">
                  Recomendaciones
                  <textarea value={form.recommendations} onChange={(e) => setForm((p) => ({ ...p, recommendations: e.target.value }))} />
                </label>
                <label className="checkbox-row">
                  <input
                    type="checkbox"
                    checked={form.visible_to_parents}
                    onChange={(e) => setForm((p) => ({ ...p, visible_to_parents: e.target.checked }))}
                  />
                  Visible para el tutor
                </label>
                <label className="span-2">
                  Adjuntos
                  <input
                    ref={documentAttachmentInputRef}
                    type="file"
                    multiple
                    accept="application/pdf,image/png,image/jpeg,image/webp,image/gif"
                    onChange={onDocumentAttachmentSelected}
                  />
                </label>
                {(savedAttachments.length > 0 || form.attachments.length > 0) ? (
                  <div className="span-2 attachment-card-grid">
                    {savedAttachments.map((attachment) => (
                      <div key={attachment.id} className="attachment-card attachment-card-preview">
                        <button
                          type="button"
                          className="attachment-card-body"
                          onClick={() => downloadDocumentAttachment(attachment)}
                          title="Descargar adjunto"
                        >
                          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" className="attachment-card-icon">
                            <path d="M6 2h9l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2zm8 1.5V8h4.5L14 3.5z" />
                          </svg>
                          <span className="attachment-card-name">{attachment.name}</span>
                        </button>
                        <div className="attachment-card-footer">
                          <select
                            className="attachment-card-permission"
                            value={Number(attachment.allow_download) === 1 ? '1' : '0'}
                            onChange={(e) => updateSavedAttachmentPermission(attachment.id, e.target.value === '1')}
                          >
                            <option value="0">Solo visible</option>
                            <option value="1">Visible y descarga</option>
                          </select>
                          <IconActionButton
                            icon="delete"
                            tone="danger"
                            label="Quitar adjunto"
                            onClick={() => removeSavedAttachment(attachment.id)}
                          />
                        </div>
                      </div>
                    ))}
                    {form.attachments.map((attachment, index) => (
                      <div key={`pending-${index}`} className="attachment-card attachment-card-preview">
                        <button
                          type="button"
                          className="attachment-card-body"
                          onClick={() => downloadDocumentAttachment(attachment)}
                          title="Descargar adjunto"
                        >
                          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" className="attachment-card-icon">
                            <path d="M6 2h9l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2zm8 1.5V8h4.5L14 3.5z" />
                          </svg>
                          <span className="attachment-card-name">{attachment.name}</span>
                        </button>
                        <div className="attachment-card-footer">
                          <select
                            className="attachment-card-permission"
                            value={attachment.allow_download ? '1' : '0'}
                            onChange={(e) => updatePendingAttachmentPermission(index, e.target.value === '1')}
                          >
                            <option value="0">Solo visible</option>
                            <option value="1">Visible y descarga</option>
                          </select>
                          <IconActionButton
                            icon="delete"
                            tone="danger"
                            label="Quitar adjunto"
                            onClick={() => removePendingAttachment(index)}
                          />
                        </div>
                      </div>
                    ))}
                  </div>
                ) : null}
                <div className="inline-actions span-2">
                  <button className="button button-primary" type="submit">
                    {editingId ? 'Guardar cambios' : 'Crear ficha'}
                  </button>
                  <button type="button" className="button button-ghost" onClick={closeModal}>
                    Cancelar
                  </button>
                </div>
              </form>
            </div>
          </div>
        </div>
      ) : null}

      <ConfirmActionModal
        open={Boolean(documentToDelete)}
        title="Eliminar ficha"
        message={documentToDelete ? `Vas a eliminar la ficha "${documentToDelete.title}".` : ''}
        onCancel={() => setDocumentToDelete(null)}
        onConfirm={remove}
        confirmLabel="Eliminar"
      />
    </section>
  );
}

function TherapiesPage() {
  const { notify } = useApp();
  const [items, setItems] = useState([]);
  const [form, setForm] = useState({ name: '', description: '' });
  const [editingId, setEditingId] = useState(0);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [therapyToDelete, setTherapyToDelete] = useState(null);

  const load = useCallback(async () => {
    try {
      const response = await api('/therapies');
      setItems(response.therapies || []);
    } catch (error) {
      notify('error', error.message || 'No se pudieron cargar terapias.');
    }
  }, [notify]);

  useEffect(() => {
    load();
  }, [load]);

  const resetForm = () => {
    setEditingId(0);
    setForm({ name: '', description: '' });
  };

  const openCreateModal = () => {
    resetForm();
    setIsModalOpen(true);
  };

  const openEditModal = (item) => {
    setEditingId(item.id);
    setForm({ name: item.name, description: item.description || '' });
    setIsModalOpen(true);
  };

  const closeModal = () => {
    setIsModalOpen(false);
    resetForm();
  };

  const submit = async (event) => {
    event.preventDefault();

    try {
      if (editingId) {
        await api(`/therapies/${editingId}`, { method: 'PUT', body: form });
        notify('success', 'Terapia actualizada.');
      } else {
        await api('/therapies', { method: 'POST', body: form });
        notify('success', 'Terapia creada.');
      }
      closeModal();
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo guardar.');
    }
  };

  const remove = async () => {
    if (!therapyToDelete) {
      return;
    }

    try {
      await api(`/therapies/${therapyToDelete.id}`, { method: 'DELETE' });
      notify('success', 'Terapia eliminada.');
      setTherapyToDelete(null);
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar.');
    }
  };

  return (
    <section className="list-card">
      <div className="section-head">
        <h2>Terapias</h2>
        <button type="button" className="button button-primary" onClick={openCreateModal}>
          Nueva terapia
        </button>
      </div>

      <SimpleTable
        columns={['Nombre', 'Descripcion', 'Acciones']}
        rows={items.map((item) => [
          item.name,
          item.description || '-',
          <div className="action-row" key={`therapy-actions-${item.id}`}>
            <IconActionButton icon="edit" label="Editar" onClick={() => openEditModal(item)} />
            <IconActionButton icon="delete" label="Eliminar" tone="danger" onClick={() => setTherapyToDelete(item)} />
          </div>,
        ])}
      />

      {isModalOpen ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={editingId ? 'Editar terapia' : 'Nueva terapia'}>
          <div className="modal-card">
            <div className="modal-head">
              <h3>{editingId ? 'Editar terapia' : 'Nueva terapia'}</h3>
              <button type="button" className="modal-close" onClick={closeModal} aria-label="Cerrar">
                X
              </button>
            </div>

            <form className="stack" onSubmit={submit}>
              <label>
                Nombre
                <input value={form.name} onChange={(e) => setForm((p) => ({ ...p, name: e.target.value }))} required />
              </label>
              <label>
                Descripcion
                <textarea value={form.description} onChange={(e) => setForm((p) => ({ ...p, description: e.target.value }))} />
              </label>
              <div className="inline-actions">
                <button className="button button-primary" type="submit">
                  {editingId ? 'Guardar cambios' : 'Crear terapia'}
                </button>
                <button type="button" className="button button-ghost" onClick={closeModal}>
                  Cancelar
                </button>
              </div>
            </form>
          </div>
        </div>
      ) : null}

      <ConfirmActionModal
        open={Boolean(therapyToDelete)}
        title="Eliminar terapia"
        message={therapyToDelete ? `Vas a eliminar la terapia "${therapyToDelete.name}".` : ''}
        onCancel={() => setTherapyToDelete(null)}
        onConfirm={remove}
        confirmLabel="Eliminar"
      />
    </section>
  );
}

function AssignmentsPage() {
  const { notify } = useApp();
  const [assignments, setAssignments] = useState([]);
  const [patients, setPatients] = useState([]);
  const [therapists, setTherapists] = useState([]);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [editingPatientId, setEditingPatientId] = useState(0);
  const [assignmentToDelete, setAssignmentToDelete] = useState(null);
  const [groupToDelete, setGroupToDelete] = useState(null);
  const [form, setForm] = useState({ patient_id: '', therapist_ids: [] });

  const load = useCallback(async () => {
    try {
      const response = await api('/assignments');
      setAssignments(response.assignments || []);
      setPatients(response.patients || []);
      setTherapists(response.therapists || []);
    } catch (error) {
      notify('error', error.message || 'No se pudieron cargar las asignaciones.');
    }
  }, [notify]);

  useEffect(() => {
    load();
  }, [load]);

  const groupedAssignments = useMemo(() => {
    const map = new Map();
    assignments.forEach((item) => {
      if (!map.has(item.patient_id)) {
        map.set(item.patient_id, {
          patientId: item.patient_id,
          patientName: item.patient_name,
          createdAt: item.created_at,
          items: [],
        });
      }
      const group = map.get(item.patient_id);
      group.items.push(item);
      if (new Date(item.created_at) < new Date(group.createdAt)) {
        group.createdAt = item.created_at;
      }
    });
    return Array.from(map.values());
  }, [assignments]);

  const availableTherapists = useMemo(() => {
    if (editingPatientId) {
      return therapists;
    }

    const patientId = Number(form.patient_id || 0);
    if (!patientId) {
      return therapists;
    }

    const assignedIds = new Set(
      assignments.filter((item) => item.patient_id === patientId).map((item) => item.therapist_id)
    );
    return therapists.filter((therapist) => !assignedIds.has(therapist.id));
  }, [therapists, assignments, form.patient_id, editingPatientId]);

  const openCreateModal = () => {
    setEditingPatientId(0);
    setForm({ patient_id: '', therapist_ids: [] });
    setIsModalOpen(true);
  };

  const openEditModal = (item) => {
    const patientId = item.patient_id;
    const assignedIds = assignments
      .filter((assignment) => assignment.patient_id === patientId)
      .map((assignment) => String(assignment.therapist_id));
    setEditingPatientId(patientId);
    setForm({ patient_id: String(patientId), therapist_ids: assignedIds });
    setIsModalOpen(true);
  };

  const closeModal = () => {
    setIsModalOpen(false);
    setEditingPatientId(0);
    setForm({ patient_id: '', therapist_ids: [] });
  };

  const toggleTherapistSelection = (therapistId) => {
    const idStr = String(therapistId);
    setForm((prev) => {
      const exists = prev.therapist_ids.includes(idStr);
      return {
        ...prev,
        therapist_ids: exists
          ? prev.therapist_ids.filter((id) => id !== idStr)
          : [...prev.therapist_ids, idStr],
      };
    });
  };

  const submit = async (event) => {
    event.preventDefault();

    const patientId = Number(form.patient_id || 0);

    if (editingPatientId) {
      const existingForPatient = assignments.filter((assignment) => assignment.patient_id === patientId);
      const existingTherapistIds = new Set(existingForPatient.map((assignment) => String(assignment.therapist_id)));
      const selectedIds = new Set(form.therapist_ids);

      const toRemove = existingForPatient.filter((assignment) => !selectedIds.has(String(assignment.therapist_id)));
      const toAdd = form.therapist_ids.filter((id) => !existingTherapistIds.has(id));

      if (!toRemove.length && !toAdd.length) {
        notify('error', 'No hay cambios que guardar.');
        return;
      }

      try {
        for (const assignment of toRemove) {
          await api(`/assignments/${assignment.id}`, { method: 'DELETE' });
        }
        if (toAdd.length) {
          await api('/assignments', {
            method: 'POST',
            body: { patient_id: patientId, therapist_ids: toAdd.map((id) => Number(id)) },
          });
        }
        notify('success', 'Asignaciones actualizadas.');
        closeModal();
        await load();
      } catch (error) {
        notify('error', error.message || 'No se pudieron actualizar las asignaciones.');
      }
      return;
    }

    if (!form.therapist_ids.length) {
      notify('error', 'Selecciona al menos un terapeuta.');
      return;
    }

    try {
      const response = await api('/assignments', {
        method: 'POST',
        body: {
          patient_id: patientId,
          therapist_ids: form.therapist_ids.map((id) => Number(id)),
        },
      });
      const created = response.created || 0;
      notify('success', created > 1 ? `${created} asignaciones creadas.` : 'Asignación creada.');
      closeModal();
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo crear la asignación.');
    }
  };

  const remove = async () => {
    if (!assignmentToDelete) {
      return;
    }

    try {
      await api(`/assignments/${assignmentToDelete.id}`, { method: 'DELETE' });
      notify('success', 'Asignación eliminada.');
      setAssignmentToDelete(null);
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar la asignación.');
    }
  };

  const removeGroup = async () => {
    if (!groupToDelete) {
      return;
    }

    try {
      for (const item of groupToDelete.items) {
        await api(`/assignments/${item.id}`, { method: 'DELETE' });
      }
      notify('success', 'Asignaciones eliminadas.');
      setGroupToDelete(null);
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudieron eliminar las asignaciones.');
    }
  };

  return (
    <section className="list-card">
      <div className="section-head">
        <h2>Asignaciones</h2>
        <button type="button" className="button button-primary" onClick={openCreateModal}>
          Nueva asignación
        </button>
      </div>

      <SimpleTable
        columns={['Niño', 'Terapeuta', 'Especialidad', 'Fecha', 'Acciones']}
        rows={groupedAssignments.map((group) => [
          group.patientName,
          <div className="assignment-therapist-list" key={`therapists-${group.patientId}`}>
            {group.items.map((item) => (
              <div className="assignment-therapist-row" key={`therapist-${item.id}`}>
                <span>{item.therapist_name}</span>
                <button
                  type="button"
                  className="assignment-therapist-remove"
                  aria-label={`Quitar a ${item.therapist_name}`}
                  title={`Quitar a ${item.therapist_name}`}
                  onClick={() => setAssignmentToDelete(item)}
                >
                  ×
                </button>
              </div>
            ))}
          </div>,
          <div className="assignment-therapist-list" key={`specialties-${group.patientId}`}>
            {group.items.map((item) => (
              <div className="assignment-therapist-row" key={`specialty-${item.id}`}>
                {item.therapist_specialty || '-'}
              </div>
            ))}
          </div>,
          formatDate(group.createdAt),
          <div className="action-row" key={`assignment-actions-${group.patientId}`}>
            <IconActionButton
              icon="edit"
              label="Editar"
              onClick={() => openEditModal({ patient_id: group.patientId })}
            />
            <IconActionButton
              icon="delete"
              label="Quitar asignaciones"
              tone="danger"
              onClick={() => setGroupToDelete(group)}
            />
          </div>,
        ])}
      />

      {isModalOpen ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={editingPatientId ? 'Editar asignaciones' : 'Nueva asignación'}>
          <div className="modal-card">
            <div className="modal-head">
              <h3>{editingPatientId ? 'Editar asignaciones' : 'Nueva asignación'}</h3>
              <button type="button" className="modal-close" onClick={closeModal} aria-label="Cerrar">
                X
              </button>
            </div>

            <form className="stack" onSubmit={submit}>
              <label>
                Niño
                <select
                  value={form.patient_id}
                  onChange={(event) => setForm((prev) => ({ ...prev, patient_id: event.target.value }))}
                  disabled={Boolean(editingPatientId)}
                  required
                >
                  <option value="">Seleccionar</option>
                  {patients.map((patient) => (
                    <option key={patient.id} value={patient.id}>
                      {patient.first_name} {patient.last_name}
                    </option>
                  ))}
                </select>
              </label>

              <div>
                Terapeutas
                <div className="checkbox-list">
                  {availableTherapists.length ? (
                    availableTherapists.map((therapist) => (
                      <label key={therapist.id} className="checkbox-row">
                        <input
                          type="checkbox"
                          checked={form.therapist_ids.includes(String(therapist.id))}
                          onChange={() => toggleTherapistSelection(therapist.id)}
                        />
                        {therapist.name}{therapist.specialty ? ` - ${therapist.specialty}` : ''}
                      </label>
                    ))
                  ) : (
                    <p className="empty-state">
                      {form.patient_id
                        ? 'Este niño ya tiene asignadas todas las terapeutas disponibles.'
                        : 'No hay terapeutas disponibles.'}
                    </p>
                  )}
                </div>
              </div>

              <div className="inline-actions">
                <button className="button button-primary" type="submit">
                  {editingPatientId ? 'Guardar cambios' : 'Guardar asignación'}
                </button>
                <button type="button" className="button button-ghost" onClick={closeModal}>
                  Cancelar
                </button>
              </div>
            </form>
          </div>
        </div>
      ) : null}

      <ConfirmActionModal
        open={Boolean(assignmentToDelete)}
        title="Quitar asignación"
        message={assignmentToDelete ? `Vas a quitar la asignación de ${assignmentToDelete.patient_name} con ${assignmentToDelete.therapist_name}.` : ''}
        onCancel={() => setAssignmentToDelete(null)}
        onConfirm={remove}
        confirmLabel="Quitar"
      />

      <ConfirmActionModal
        open={Boolean(groupToDelete)}
        title="Quitar asignaciones"
        message={groupToDelete ? `Vas a quitar todas las asignaciones de ${groupToDelete.patientName} (${groupToDelete.items.map((item) => item.therapist_name).join(', ')}).` : ''}
        onCancel={() => setGroupToDelete(null)}
        onConfirm={removeGroup}
        confirmLabel="Quitar"
      />
    </section>
  );
}

function UsersPage() {
  const { notify } = useApp();
  const [users, setUsers] = useState([]);
  const [patients, setPatients] = useState([]);
  const [canManageAdmins, setCanManageAdmins] = useState(false);
  const [editingId, setEditingId] = useState(0);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [userToDelete, setUserToDelete] = useState(null);
  const [form, setForm] = useState({
    name: '',
    last_name: '',
    username: '',
    password: '',
    roles: ['terapeuta'],
    specialty: '',
    linked_patient_id: '',
    new_patient_first_name: '',
    new_patient_last_name: '',
    tutor_phone: '',
    tutor_email: '',
  });

  const load = useCallback(async () => {
    try {
      const response = await api('/users');
      setUsers(response.users || []);
      setPatients(response.patients || []);
      setCanManageAdmins(Boolean(response.canManageAdmins));
    } catch (error) {
      notify('error', error.message || 'No se pudieron cargar usuarios.');
    }
  }, [notify]);

  const splitUserName = (fullName) => {
    const raw = String(fullName || '').trim();
    if (!raw) {
      return { firstName: '', lastName: '' };
    }

    const parts = raw.split(/\s+/);
    if (parts.length === 1) {
      return { firstName: parts[0], lastName: '' };
    }

    return {
      firstName: parts[0],
      lastName: parts.slice(1).join(' '),
    };
  };

  useEffect(() => {
    load();
  }, [load]);

  const resetForm = () => {
    setEditingId(0);
    setForm({
      name: '',
      last_name: '',
      username: '',
      password: '',
      roles: ['terapeuta'],
      specialty: '',
      linked_patient_id: '',
      new_patient_first_name: '',
      new_patient_last_name: '',
      tutor_phone: '',
      tutor_email: '',
    });
  };

  const openCreateModal = () => {
    resetForm();
    setIsModalOpen(true);
  };

  const openEditModal = (user) => {
    const splitName = splitUserName(user.name);
    const userRoles = Array.isArray(user.roles) && user.roles.length ? user.roles : [user.role];

    setEditingId(user.id);
    setForm({
      name: splitName.firstName,
      last_name: splitName.lastName,
      username: user.username,
      password: '',
      roles: userRoles,
      specialty: user.specialty || '',
      linked_patient_id: user.linked_patient_id || '',
      new_patient_first_name: '',
      new_patient_last_name: '',
      tutor_phone: user.tutor_phone || '',
      tutor_email: user.tutor_email || '',
    });
    setIsModalOpen(true);
  };

  const closeModal = () => {
    setIsModalOpen(false);
    resetForm();
  };

  const submit = async (event) => {
    event.preventDefault();

    const isUsuario = form.roles.includes('usuario');

    const payload = {
      ...form,
      name: `${String(form.name || '').trim()} ${String(form.last_name || '').trim()}`.trim(),
      linked_patient_id: isUsuario ? Number(form.linked_patient_id || 0) : null,
      new_patient_first_name: isUsuario ? String(form.new_patient_first_name || '').trim() : '',
      new_patient_last_name: isUsuario ? String(form.new_patient_last_name || '').trim() : '',
      tutor_phone: isUsuario ? String(form.tutor_phone || '').trim() : '',
      tutor_email: isUsuario ? String(form.tutor_email || '').trim() : '',
      specialty: form.specialty,
    };

    try {
      if (editingId) {
        await api(`/users/${editingId}`, { method: 'PUT', body: payload });
        notify('success', 'Usuario actualizado.');
      } else {
        await api('/users', { method: 'POST', body: payload });
        notify('success', 'Usuario creado.');
      }
      closeModal();
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo guardar el usuario.');
    }
  };

  const remove = async () => {
    if (!userToDelete) {
      return;
    }

    try {
      await api(`/users/${userToDelete.id}`, { method: 'DELETE' });
      notify('success', 'Usuario eliminado.');
      setUserToDelete(null);
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar el usuario.');
    }
  };

  return (
    <section className="list-card">
      <div className="section-head">
        <h2>Usuarios y terapeutas</h2>
        <button type="button" className="button button-primary" onClick={openCreateModal}>
          Nuevo usuario
        </button>
      </div>

      <SimpleTable
        columns={['Nombre', 'Usuario', 'Rol', 'Detalle', 'Acciones']}
        rows={users.map((user) => {
          const userRoles = Array.isArray(user.roles) && user.roles.length ? user.roles : [user.role];
          const details = [];

          if (userRoles.includes('terapeuta') && user.specialty) {
            details.push(user.specialty);
          }

          if (userRoles.includes('usuario')) {
            details.push(`${user.linked_patient_name || 'Sin niño'} | ${user.tutor_phone || '-'} | ${user.tutor_email || '-'}`);
          }

          return [
            user.name,
            user.username,
            userRoles.join(', '),
            details.join(' | ') || '-',
            <div className="action-row" key={`user-actions-${user.id}`}>
              {user.role === 'root' ? (
                <span>Protegido</span>
              ) : (
                <>
                  <IconActionButton icon="edit" label="Editar" onClick={() => openEditModal(user)} />
                  <IconActionButton icon="delete" label="Eliminar" tone="danger" onClick={() => setUserToDelete(user)} />
                </>
              )}
            </div>,
          ];
        })}
      />

      {isModalOpen ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={editingId ? 'Editar usuario' : 'Nuevo usuario'}>
          <div className="modal-card">
            <div className="modal-head">
              <h3>{editingId ? 'Editar usuario' : 'Nuevo usuario'}</h3>
              <button type="button" className="modal-close" onClick={closeModal} aria-label="Cerrar">
                X
              </button>
            </div>

            <form className="stack grid-2" onSubmit={submit}>
              <label className="span-2">
                Roles
                <div className="checkbox-list">
                  {(canManageAdmins ? ['admin', 'terapeuta', 'usuario'] : ['terapeuta', 'usuario']).map((roleOption) => (
                    <label key={roleOption} className="checkbox-row">
                      <input
                        type="checkbox"
                        checked={form.roles.includes(roleOption)}
                        disabled={!canManageAdmins}
                        onChange={(e) => {
                          const checked = e.target.checked;
                          setForm((p) => {
                            const nextRoles = checked
                              ? Array.from(new Set([...p.roles, roleOption]))
                              : p.roles.filter((r) => r !== roleOption);

                            return {
                              ...p,
                              roles: nextRoles.length ? nextRoles : p.roles,
                            };
                          });
                        }}
                      />
                      {roleOption}
                    </label>
                  ))}
                </div>
              </label>

              <label>
                Nombre
                <input value={form.name} onChange={(e) => setForm((p) => ({ ...p, name: e.target.value }))} required />
              </label>
              <label>
                Apellidos
                <input value={form.last_name} onChange={(e) => setForm((p) => ({ ...p, last_name: e.target.value }))} required />
              </label>
              <label>
                Usuario
                <input value={form.username} onChange={(e) => setForm((p) => ({ ...p, username: e.target.value }))} required />
              </label>
              <label>
                Contrasena {editingId ? '(opcional)' : ''}
                <input
                  type="password"
                  value={form.password}
                  onChange={(e) => setForm((p) => ({ ...p, password: e.target.value }))}
                  required={!editingId}
                />
              </label>
              {form.roles.includes('terapeuta') ? (
                <label>
                  Especialidad
                  <input
                    value={form.specialty}
                    onChange={(e) => setForm((p) => ({ ...p, specialty: e.target.value }))}
                  />
                </label>
              ) : null}

              {form.roles.includes('usuario') ? (
                <>
                  {editingId ? (
                    <label>
                      Niño asociado
                      <select
                        value={form.linked_patient_id}
                        onChange={(e) => setForm((p) => ({ ...p, linked_patient_id: e.target.value }))}
                        required
                      >
                        <option value="">Seleccionar niño</option>
                        {patients.map((patient) => (
                          <option key={patient.id} value={patient.id}>
                            {patient.first_name} {patient.last_name}
                          </option>
                        ))}
                      </select>
                    </label>
                  ) : (
                    <>
                      <label>
                        Nombre del niño
                        <input
                          value={form.new_patient_first_name}
                          onChange={(e) => setForm((p) => ({ ...p, new_patient_first_name: e.target.value }))}
                          required
                        />
                      </label>
                      <label>
                        Apellidos del niño
                        <input
                          value={form.new_patient_last_name}
                          onChange={(e) => setForm((p) => ({ ...p, new_patient_last_name: e.target.value }))}
                          required
                        />
                      </label>
                    </>
                  )}
                  <label>
                    Telefono tutor
                    <input
                      value={form.tutor_phone}
                      onChange={(e) => setForm((p) => ({ ...p, tutor_phone: e.target.value }))}
                      required
                    />
                  </label>
                  <label>
                    Correo tutor
                    <input
                      type="email"
                      value={form.tutor_email}
                      onChange={(e) => setForm((p) => ({ ...p, tutor_email: e.target.value }))}
                      required
                    />
                  </label>
                </>
              ) : null}

              <div className="inline-actions span-2 session-modal-actions">
                <button className="button button-primary" type="submit">
                  {editingId ? 'Guardar cambios' : 'Crear usuario'}
                </button>
                <button type="button" className="button button-ghost" onClick={closeModal}>
                  Cancelar
                </button>
              </div>
            </form>
          </div>
        </div>
      ) : null}

      <ConfirmActionModal
        open={Boolean(userToDelete)}
        title="Eliminar usuario"
        message={userToDelete ? `Vas a eliminar al usuario ${userToDelete.username}.` : ''}
        onCancel={() => setUserToDelete(null)}
        onConfirm={remove}
        confirmLabel="Eliminar"
      />
    </section>
  );
}

function ConfirmActionModal({ open, title, message, onCancel, onConfirm, confirmLabel = 'Eliminar' }) {
  if (!open) {
    return null;
  }

  return (
    <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={title}>
      <div className="modal-card">
        <div className="modal-head">
          <h3>{title}</h3>
          <button type="button" className="modal-close" onClick={onCancel} aria-label="Cerrar">
            X
          </button>
        </div>

        <p>{message}</p>
        <p>Esta acción no se puede deshacer.</p>

        <div className="inline-actions session-modal-actions">
          <button type="button" className="button button-ghost" onClick={onCancel}>
            Cancelar
          </button>
          <button type="button" className="button button-danger" onClick={onConfirm}>
            {confirmLabel}
          </button>
        </div>
      </div>
    </div>
  );
}

function emptyMeetingMinute() {
  return {
    meeting_date: '',
    agenda: '',
    development: '',
    decisions: '',
    attendees: [],
    signatures: [],
    audio_name: '',
    audio_type: '',
    audio_data_url: '',
    remove_audio: false,
  };
}

function MeetingMinutesPage() {
  const { notify } = useApp();
  const [items, setItems] = useState([]);
  const [form, setForm] = useState(emptyMeetingMinute());
  const [editingId, setEditingId] = useState(0);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [minuteToDelete, setMinuteToDelete] = useState(null);
  const [isRecording, setIsRecording] = useState(false);
  const [interimTranscript, setInterimTranscript] = useState('');
  const meetingDateInputRef = useRef(null);
  const meetingDatePickerRef = useRef(null);
  const speechSupportedRef = useRef(Boolean(window.SpeechRecognition || window.webkitSpeechRecognition));
  const mediaRecorderRef = useRef(null);
  const audioChunksRef = useRef([]);
  const recognitionRef = useRef(null);
  const mediaStreamRef = useRef(null);
  const isRecordingRef = useRef(false);

  const load = useCallback(async () => {
    try {
      const response = await api('/meeting-minutes');
      setItems(response.meetingMinutes || []);
    } catch (error) {
      notify('error', error.message || 'No se pudieron cargar las actas.');
    }
  }, [notify]);

  useEffect(() => {
    load();
  }, [load]);

  useEffect(() => {
    if (!isModalOpen) {
      return undefined;
    }

    const input = meetingDateInputRef.current;
    if (!input || !window.flatpickr) {
      return undefined;
    }

    const picker = window.flatpickr(input, {
      locale: (window.flatpickr.l10ns && window.flatpickr.l10ns.es) || 'default',
      dateFormat: 'Y-m-d',
      altInput: true,
      altFormat: 'd/m/Y',
      defaultDate: form.meeting_date || null,
      allowInput: false,
      disableMobile: true,
      onChange: (selectedDates) => {
        if (!selectedDates.length) {
          setForm((p) => ({ ...p, meeting_date: '' }));
          return;
        }

        const selectedDate = selectedDates[0];
        const normalized = `${selectedDate.getFullYear()}-${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}`;
        setForm((p) => ({ ...p, meeting_date: normalized }));
      },
    });

    meetingDatePickerRef.current = picker;

    return () => {
      picker.destroy();
      meetingDatePickerRef.current = null;
    };
  }, [isModalOpen, editingId]);

  const openMeetingDatePicker = () => {
    const picker = meetingDatePickerRef.current;
    if (picker) {
      picker.open();
    }
  };

  const stopRecording = useCallback(() => {
    isRecordingRef.current = false;
    setIsRecording(false);
    setInterimTranscript('');

    if (recognitionRef.current) {
      recognitionRef.current.onend = null;
      try {
        recognitionRef.current.stop();
      } catch (error) {
        // ya estaba detenido
      }
      recognitionRef.current = null;
    }

    if (mediaRecorderRef.current && mediaRecorderRef.current.state !== 'inactive') {
      mediaRecorderRef.current.stop();
    }
    mediaRecorderRef.current = null;

    if (mediaStreamRef.current) {
      mediaStreamRef.current.getTracks().forEach((track) => track.stop());
      mediaStreamRef.current = null;
    }
  }, []);

  useEffect(() => {
    if (!isModalOpen) {
      stopRecording();
    }
  }, [isModalOpen, stopRecording]);

  useEffect(() => () => stopRecording(), [stopRecording]);

  const startRecording = async () => {
    if (window.isSecureContext === false) {
      notify(
        'error',
        'El navegador bloquea el microfono porque la pagina no se sirve en HTTPS (ni en localhost). Accede por https:// o desde localhost para poder grabar.'
      );
      return;
    }

    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
      notify('error', 'Este navegador no permite grabar audio.');
      return;
    }

    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      mediaStreamRef.current = stream;
      audioChunksRef.current = [];

      const supportedMimeType = [
        'audio/webm;codecs=opus',
        'audio/webm',
        'audio/ogg;codecs=opus',
        'audio/mp4',
      ].find((candidate) => window.MediaRecorder && MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported(candidate));

      const recorder = supportedMimeType ? new MediaRecorder(stream, { mimeType: supportedMimeType }) : new MediaRecorder(stream);
      recorder.ondataavailable = (event) => {
        if (event.data && event.data.size > 0) {
          audioChunksRef.current.push(event.data);
        }
      };
      recorder.onstop = () => {
        const blob = new Blob(audioChunksRef.current, { type: recorder.mimeType || 'audio/webm' });
        const reader = new FileReader();
        reader.onloadend = () => {
          setForm((p) => ({
            ...p,
            audio_name: `grabacion-reunion-${Date.now()}.webm`,
            audio_type: blob.type,
            audio_data_url: reader.result,
            remove_audio: false,
          }));
        };
        reader.readAsDataURL(blob);
      };
      recorder.start();
      mediaRecorderRef.current = recorder;

      const SpeechRecognitionImpl = window.SpeechRecognition || window.webkitSpeechRecognition;
      if (SpeechRecognitionImpl) {
        const recognition = new SpeechRecognitionImpl();
        recognition.lang = 'es-ES';
        recognition.continuous = true;
        recognition.interimResults = true;

        recognition.onresult = (event) => {
          let finalText = '';
          let interimText = '';

          for (let i = event.resultIndex; i < event.results.length; i += 1) {
            const result = event.results[i];
            if (result.isFinal) {
              finalText += `${result[0].transcript} `;
            } else {
              interimText += result[0].transcript;
            }
          }

          if (finalText.trim()) {
            setForm((p) => ({
              ...p,
              development: p.development ? `${p.development} ${finalText}`.trim() : finalText.trim(),
            }));
          }
          setInterimTranscript(interimText);
        };

        recognition.onerror = (event) => {
          if (event.error !== 'no-speech' && event.error !== 'aborted') {
            notify('error', 'Error en la transcripcion por voz.');
          }
        };

        recognition.onend = () => {
          if (isRecordingRef.current) {
            try {
              recognition.start();
            } catch (error) {
              // evita doble arranque si ya estaba iniciado
            }
          }
        };

        try {
          recognition.start();
          recognitionRef.current = recognition;
        } catch (error) {
          // el navegador no permitio iniciar el reconocimiento de voz
        }
      }

      isRecordingRef.current = true;
      setIsRecording(true);
    } catch (error) {
      if (error && error.name === 'NotAllowedError') {
        notify('error', 'Permiso de microfono denegado. Revisa los permisos del sitio en el navegador.');
      } else if (error && error.name === 'NotFoundError') {
        notify('error', 'No se detecto ningun microfono en este dispositivo.');
      } else if (error && error.name === 'NotReadableError') {
        notify('error', 'El microfono esta siendo usado por otra aplicacion.');
      } else {
        notify('error', `No se pudo acceder al microfono. ${(error && error.message) || ''}`.trim());
      }
    }
  };

  const removeAudio = () => {
    setForm((p) => ({ ...p, audio_name: '', audio_type: '', audio_data_url: '', remove_audio: true }));
  };

  const resetForm = () => {
    setEditingId(0);
    setForm(emptyMeetingMinute());
  };

  const openCreateModal = () => {
    resetForm();
    setIsModalOpen(true);
  };

  const openEditModal = async (item) => {
    try {
      const response = await api(`/meeting-minutes/${item.id}`);
      const minute = response.meetingMinute;
      setEditingId(minute.id);
      setForm({
        meeting_date: minute.meeting_date || '',
        agenda: minute.agenda || '',
        development: minute.development || '',
        decisions: minute.decisions || '',
        attendees: (minute.attendees || []).map((a) => ({ name: a.name, role: a.role || '' })),
        signatures: (minute.signatures || []).map((s) => ({ name: s.name, role: s.role || '', signed: Boolean(s.signed) })),
        audio_name: minute.audio_name || '',
        audio_type: minute.audio_type || '',
        audio_data_url: minute.audio_data_url || '',
        remove_audio: false,
      });
      setIsModalOpen(true);
    } catch (error) {
      notify('error', error.message || 'No se pudo cargar el acta.');
    }
  };

  const closeModal = () => {
    setIsModalOpen(false);
    resetForm();
  };

  const addAttendeeRow = () => {
    setForm((p) => ({ ...p, attendees: [...p.attendees, { name: '', role: '' }] }));
  };

  const updateAttendeeRow = (index, field, value) => {
    setForm((p) => ({
      ...p,
      attendees: p.attendees.map((row, i) => (i === index ? { ...row, [field]: value } : row)),
    }));
  };

  const removeAttendeeRow = (index) => {
    setForm((p) => ({ ...p, attendees: p.attendees.filter((_, i) => i !== index) }));
  };

  const addSignatureRow = () => {
    setForm((p) => ({ ...p, signatures: [...p.signatures, { name: '', role: '', signed: false }] }));
  };

  const updateSignatureRow = (index, field, value) => {
    setForm((p) => ({
      ...p,
      signatures: p.signatures.map((row, i) => (i === index ? { ...row, [field]: value } : row)),
    }));
  };

  const removeSignatureRow = (index) => {
    setForm((p) => ({ ...p, signatures: p.signatures.filter((_, i) => i !== index) }));
  };

  const submit = async (event) => {
    event.preventDefault();

    if (!form.meeting_date) {
      notify('error', 'Indica la fecha de la reunion.');
      return;
    }

    try {
      if (editingId) {
        await api(`/meeting-minutes/${editingId}`, { method: 'PUT', body: form });
        notify('success', 'Acta actualizada.');
      } else {
        await api('/meeting-minutes', { method: 'POST', body: form });
        notify('success', 'Acta creada.');
      }
      closeModal();
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo guardar el acta.');
    }
  };

  const remove = async () => {
    if (!minuteToDelete) {
      return;
    }

    try {
      await api(`/meeting-minutes/${minuteToDelete.id}`, { method: 'DELETE' });
      notify('success', 'Acta eliminada.');
      setMinuteToDelete(null);
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar el acta.');
    }
  };

  return (
    <section className="list-card">
      <div className="section-head">
        <h2>Actas de reunion</h2>
        <button type="button" className="button button-primary" onClick={openCreateModal}>
          Nueva acta
        </button>
      </div>

      <SimpleTable
        columns={['Fecha', 'Asistentes', 'Firmas', 'Audio', 'Creado por', 'Acciones']}
        rows={items.map((item) => [
          formatDate(item.meeting_date),
          item.attendee_count,
          item.signature_count,
          item.has_audio ? 'Si' : 'No',
          item.created_by_name || '-',
          <div className="action-row" key={`minute-actions-${item.id}`}>
            <IconActionButton icon="edit" label="Ver / Editar" onClick={() => openEditModal(item)} />
            <IconActionButton icon="delete" label="Eliminar" tone="danger" onClick={() => setMinuteToDelete(item)} />
          </div>,
        ])}
      />

      {isModalOpen ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={editingId ? 'Editar acta' : 'Nueva acta'}>
          <div className="modal-card modal-card-wide">
            <div className="modal-head">
              <h3>{editingId ? 'Editar acta' : 'Nueva acta'}</h3>
              <button type="button" className="modal-close" onClick={closeModal} aria-label="Cerrar">
                X
              </button>
            </div>

            <form className="stack" onSubmit={submit}>
              <label>
                Fecha de reunion
                <div className="localized-date-field">
                  <input ref={meetingDateInputRef} type="text" placeholder="DD/MM/AAAA" />
                  <button type="button" className="localized-date-trigger" aria-label="Abrir calendario" onClick={openMeetingDatePicker}>
                    <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                      <path d="M7 2h2v2h6V2h2v2h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h3V2zm13 8H4v10h16V10zM4 8h16V6H4v2z" />
                    </svg>
                  </button>
                </div>
              </label>

              <div className="dynamic-list-field">
                <span className="dynamic-list-label">Asistentes</span>
                {form.attendees.map((attendee, index) => (
                  <div className="dynamic-row" key={`attendee-${index}`}>
                    <input
                      placeholder="Nombre"
                      value={attendee.name}
                      onChange={(e) => updateAttendeeRow(index, 'name', e.target.value)}
                      required
                    />
                    <input
                      placeholder="Cargo / rol"
                      value={attendee.role}
                      onChange={(e) => updateAttendeeRow(index, 'role', e.target.value)}
                    />
                    <IconActionButton icon="delete" tone="danger" label="Quitar asistente" onClick={() => removeAttendeeRow(index)} />
                  </div>
                ))}
                <button type="button" className="button button-ghost" onClick={addAttendeeRow}>
                  + Añadir asistente
                </button>
              </div>

              <label>
                Orden de la reunion
                <textarea
                  value={form.agenda}
                  onChange={(e) => setForm((p) => ({ ...p, agenda: e.target.value }))}
                />
              </label>

              <div className="audio-record-field">
                <span className="dynamic-list-label">Grabacion y transcripcion de la reunion</span>
                <p className="form-hint">
                  {speechSupportedRef.current
                    ? 'Al grabar, el texto reconocido se añadira automaticamente al campo "Desarrollo de la reunion".'
                    : 'Tu navegador no soporta transcripcion automatica; solo se guardara el audio.'}
                </p>

                <div className="audio-record-controls">
                  {!isRecording ? (
                    <button type="button" className="button button-primary" onClick={startRecording}>
                      Iniciar grabacion
                    </button>
                  ) : (
                    <button type="button" className="button button-danger" onClick={stopRecording}>
                      <span className="recording-dot" aria-hidden="true" />
                      Detener grabacion
                    </button>
                  )}

                  {form.audio_data_url ? (
                    <button type="button" className="button button-ghost" onClick={removeAudio}>
                      Quitar grabacion
                    </button>
                  ) : null}
                </div>

                {isRecording && interimTranscript ? (
                  <p className="audio-interim-transcript">{interimTranscript}</p>
                ) : null}

                {form.audio_data_url ? (
                  <audio className="audio-record-player" controls src={form.audio_data_url} />
                ) : null}
              </div>

              <label>
                Desarrollo de la reunion
                <textarea
                  value={form.development}
                  onChange={(e) => setForm((p) => ({ ...p, development: e.target.value }))}
                />
              </label>

              <label>
                Toma de decisiones
                <textarea
                  value={form.decisions}
                  onChange={(e) => setForm((p) => ({ ...p, decisions: e.target.value }))}
                />
              </label>

              <div className="dynamic-list-field">
                <span className="dynamic-list-label">Firmas</span>
                {form.signatures.map((signature, index) => (
                  <div className="dynamic-row" key={`signature-${index}`}>
                    <input
                      placeholder="Nombre"
                      value={signature.name}
                      onChange={(e) => updateSignatureRow(index, 'name', e.target.value)}
                      required
                    />
                    <input
                      placeholder="Cargo / rol"
                      value={signature.role}
                      onChange={(e) => updateSignatureRow(index, 'role', e.target.value)}
                    />
                    <label className="dynamic-row-checkbox">
                      <input
                        type="checkbox"
                        checked={signature.signed}
                        onChange={(e) => updateSignatureRow(index, 'signed', e.target.checked)}
                      />
                      Firmado
                    </label>
                    <IconActionButton icon="delete" tone="danger" label="Quitar firma" onClick={() => removeSignatureRow(index)} />
                  </div>
                ))}
                <button type="button" className="button button-ghost" onClick={addSignatureRow}>
                  + Añadir firma
                </button>
              </div>

              <div className="inline-actions">
                <button className="button button-primary" type="submit">
                  {editingId ? 'Guardar cambios' : 'Crear acta'}
                </button>
                <button type="button" className="button button-ghost" onClick={closeModal}>
                  Cancelar
                </button>
              </div>
            </form>
          </div>
        </div>
      ) : null}

      <ConfirmActionModal
        open={Boolean(minuteToDelete)}
        title="Eliminar acta"
        message={minuteToDelete ? `Vas a eliminar el acta de la reunion del ${formatDate(minuteToDelete.meeting_date)}.` : ''}
        onCancel={() => setMinuteToDelete(null)}
        onConfirm={remove}
        confirmLabel="Eliminar"
      />
    </section>
  );
}

function BillingPage() {
  const { notify, session } = useApp();
  const orgSettings = session.orgSettings || {};
  const [enabled, setEnabled] = useState(false);
  const [items, setItems] = useState([]);
  const [patients, setPatients] = useState([]);
  const [accountingItems, setAccountingItems] = useState([]);
  const [statusFilter, setStatusFilter] = useState('all');
  const [billingYearFilter, setBillingYearFilter] = useState('all');
  const [billingMonthFilter, setBillingMonthFilter] = useState('all');
  const [billingQuarterFilter, setBillingQuarterFilter] = useState('all');
  const [billingSemesterFilter, setBillingSemesterFilter] = useState('all');
  const [accountingTypeFilter, setAccountingTypeFilter] = useState('all');
  const [accountingYearFilter, setAccountingYearFilter] = useState('all');
  const [accountingMonthFilter, setAccountingMonthFilter] = useState('all');
  const [accountingQuarterFilter, setAccountingQuarterFilter] = useState('all');
  const [accountingSemesterFilter, setAccountingSemesterFilter] = useState('all');
  const [editingId, setEditingId] = useState(0);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [entryToDelete, setEntryToDelete] = useState(null);
  const [form, setForm] = useState(emptyBillingEntry());
  const [accountingEditingId, setAccountingEditingId] = useState(0);
  const [isAccountingModalOpen, setIsAccountingModalOpen] = useState(false);
  const [accountingToDelete, setAccountingToDelete] = useState(null);
  const [accountingForm, setAccountingForm] = useState(emptyAccountingEntry());
  const accountingAttachmentInputRef = useRef(null);
  const issueDateInputRef = useRef(null);
  const issueDatePickerRef = useRef(null);
  const dueDateInputRef = useRef(null);
  const dueDatePickerRef = useRef(null);
  const entryDateInputRef = useRef(null);
  const entryDatePickerRef = useRef(null);

  const load = useCallback(async () => {
    try {
      const data = await api('/billing');
      setEnabled(Boolean(data.billingEnabled));
      setItems(Array.isArray(data.items) ? data.items : []);
      setPatients(Array.isArray(data.patients) ? data.patients : []);
      setAccountingItems(Array.isArray(data.accountingItems) ? data.accountingItems : []);
    } catch (error) {
      notify('error', error.message || 'No se pudo cargar facturacion.');
    }
  }, [notify]);

  useEffect(() => {
    load();
  }, [load]);

  const patientOptions = useMemo(() => {
    return patients.map((patient) => ({
      id: patient.id,
      label: `${patient.first_name || ''} ${patient.last_name || ''}`.trim(),
      guardianName: patient.guardian_name || '-',
    }));
  }, [patients]);

  useEffect(() => {
    if (!isModalOpen) {
      return undefined;
    }

    const issueInput = issueDateInputRef.current;
    const dueInput = dueDateInputRef.current;
    if (!issueInput || !dueInput || !window.flatpickr) {
      return undefined;
    }

    const issuePicker = window.flatpickr(issueInput, {
      locale: (window.flatpickr.l10ns && window.flatpickr.l10ns.es) || 'default',
      dateFormat: 'Y-m-d',
      altInput: true,
      altFormat: 'd/m/Y',
      defaultDate: form.issue_date || null,
      allowInput: false,
      disableMobile: true,
      onChange: (selectedDates) => {
        if (!selectedDates.length) {
          setForm((prev) => ({ ...prev, issue_date: '' }));
          return;
        }

        const selectedDate = selectedDates[0];
        const normalized = `${selectedDate.getFullYear()}-${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}`;
        setForm((prev) => ({ ...prev, issue_date: normalized }));
      },
    });

    const duePicker = window.flatpickr(dueInput, {
      locale: (window.flatpickr.l10ns && window.flatpickr.l10ns.es) || 'default',
      dateFormat: 'Y-m-d',
      altInput: true,
      altFormat: 'd/m/Y',
      defaultDate: form.due_date || null,
      allowInput: false,
      disableMobile: true,
      onChange: (selectedDates) => {
        if (!selectedDates.length) {
          setForm((prev) => ({ ...prev, due_date: '' }));
          return;
        }

        const selectedDate = selectedDates[0];
        const normalized = `${selectedDate.getFullYear()}-${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}`;
        setForm((prev) => ({ ...prev, due_date: normalized }));
      },
    });

    issueDatePickerRef.current = issuePicker;
    dueDatePickerRef.current = duePicker;

    return () => {
      issuePicker.destroy();
      duePicker.destroy();
      issueDatePickerRef.current = null;
      dueDatePickerRef.current = null;
    };
  }, [isModalOpen, editingId]);

  const openIssueDatePicker = () => {
    const picker = issueDatePickerRef.current;
    if (picker) {
      picker.open();
    }
  };

  const openDueDatePicker = () => {
    const picker = dueDatePickerRef.current;
    if (picker) {
      picker.open();
    }
  };

  useEffect(() => {
    if (!isAccountingModalOpen) {
      return undefined;
    }

    const input = entryDateInputRef.current;
    if (!input || !window.flatpickr) {
      return undefined;
    }

    const picker = window.flatpickr(input, {
      locale: (window.flatpickr.l10ns && window.flatpickr.l10ns.es) || 'default',
      dateFormat: 'Y-m-d',
      altInput: true,
      altFormat: 'd/m/Y',
      defaultDate: accountingForm.entry_date || null,
      allowInput: false,
      disableMobile: true,
      onChange: (selectedDates) => {
        if (!selectedDates.length) {
          setAccountingForm((prev) => ({ ...prev, entry_date: '' }));
          return;
        }

        const selectedDate = selectedDates[0];
        const normalized = `${selectedDate.getFullYear()}-${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}`;
        setAccountingForm((prev) => ({ ...prev, entry_date: normalized }));
      },
    });

    entryDatePickerRef.current = picker;

    return () => {
      picker.destroy();
      entryDatePickerRef.current = null;
    };
  }, [isAccountingModalOpen, accountingEditingId]);

  const openEntryDatePicker = () => {
    const picker = entryDatePickerRef.current;
    if (picker) {
      picker.open();
    }
  };

  const billingOptions = useMemo(() => {
    return items.map((item) => ({
      id: item.id,
      label: `${orgSettings.invoicePrefix || 'FAC-'}${String(item.id).padStart(4, '0')} - ${item.patient_name || ''} - ${item.concept || ''}`,
    }));
  }, [items, orgSettings.invoicePrefix]);

  const getEffectiveStatus = useCallback((item) => {
    if (!item) {
      return 'pending';
    }

    if (item.status === 'paid' || item.status === 'cancelled' || item.status === 'overdue') {
      return item.status;
    }

    if (item.status === 'pending' && item.due_date) {
      const todayIso = new Date().toISOString().slice(0, 10);
      if (item.due_date < todayIso) {
        return 'overdue';
      }
    }

    return item.status || 'pending';
  }, []);

  const billingYearOptions = useMemo(() => {
    const years = Array.from(new Set(items.map((item) => getYearFromDateString(item.issue_date))));
    return years.sort((yearA, yearB) => {
      if (yearA === 'Sin ano') {
        return 1;
      }
      if (yearB === 'Sin ano') {
        return -1;
      }
      return Number(yearB) - Number(yearA);
    });
  }, [items]);

  const dateFilteredItems = useMemo(() => {
    return items.filter((item) => {
      const matchesYear = billingYearFilter === 'all' || getYearFromDateString(item.issue_date) === billingYearFilter;
      const matchesMonth = billingMonthFilter === 'all' || getMonthFromDateString(item.issue_date) === billingMonthFilter;
      const matchesQuarter = billingQuarterFilter === 'all' || getQuarterFromDateString(item.issue_date) === billingQuarterFilter;
      const matchesSemester = billingSemesterFilter === 'all' || getSemesterFromDateString(item.issue_date) === billingSemesterFilter;
      return matchesYear && matchesMonth && matchesQuarter && matchesSemester;
    });
  }, [items, billingYearFilter, billingMonthFilter, billingQuarterFilter, billingSemesterFilter]);

  const visibleItems = useMemo(() => {
    if (statusFilter === 'all') {
      return dateFilteredItems;
    }

    return dateFilteredItems.filter((item) => getEffectiveStatus(item) === statusFilter);
  }, [dateFilteredItems, statusFilter, getEffectiveStatus]);

  const summary = useMemo(() => {
    const base = {
      total: 0,
      pending: 0,
      paid: 0,
      overdue: 0,
    };

    dateFilteredItems.forEach((item) => {
      const { totalAmount } = calculateBillingAmounts(item.amount_cents, item.tax_rate);
      const status = getEffectiveStatus(item);
      base.total += totalAmount;

      if (status === 'paid') {
        base.paid += totalAmount;
      } else if (status === 'overdue') {
        base.overdue += totalAmount;
      } else if (status === 'pending') {
        base.pending += totalAmount;
      }
    });

    return base;
  }, [dateFilteredItems, getEffectiveStatus]);

  const accountingYearOptions = useMemo(() => {
    const years = Array.from(new Set(accountingItems.map((item) => getYearFromDateString(item.entry_date))));
    return years.sort((yearA, yearB) => {
      if (yearA === 'Sin ano') {
        return 1;
      }
      if (yearB === 'Sin ano') {
        return -1;
      }
      return Number(yearB) - Number(yearA);
    });
  }, [accountingItems]);

  const dateFilteredAccountingItems = useMemo(() => {
    return accountingItems.filter((item) => {
      const matchesYear = accountingYearFilter === 'all' || getYearFromDateString(item.entry_date) === accountingYearFilter;
      const matchesMonth = accountingMonthFilter === 'all' || getMonthFromDateString(item.entry_date) === accountingMonthFilter;
      const matchesQuarter = accountingQuarterFilter === 'all' || getQuarterFromDateString(item.entry_date) === accountingQuarterFilter;
      const matchesSemester = accountingSemesterFilter === 'all' || getSemesterFromDateString(item.entry_date) === accountingSemesterFilter;
      return matchesYear && matchesMonth && matchesQuarter && matchesSemester;
    });
  }, [accountingItems, accountingYearFilter, accountingMonthFilter, accountingQuarterFilter, accountingSemesterFilter]);

  useEffect(() => {
    if (billingYearFilter !== 'all' && !billingYearOptions.includes(billingYearFilter)) {
      setBillingYearFilter('all');
    }
  }, [billingYearOptions, billingYearFilter]);

  useEffect(() => {
    if (accountingYearFilter !== 'all' && !accountingYearOptions.includes(accountingYearFilter)) {
      setAccountingYearFilter('all');
    }
  }, [accountingYearOptions, accountingYearFilter]);

  const accountingSummary = useMemo(() => {
    const base = {
      income: 0,
      expense: 0,
      balance: 0,
    };

    dateFilteredAccountingItems.forEach((item) => {
      const amount = Number(item.amount_cents || 0) / 100;
      if (item.entry_type === 'income') {
        base.income += amount;
      } else {
        base.expense += amount;
      }
    });

    base.balance = base.income - base.expense;
    return base;
  }, [dateFilteredAccountingItems]);

  const visibleAccountingItems = useMemo(() => {
    if (accountingTypeFilter === 'all') {
      return dateFilteredAccountingItems;
    }

    return dateFilteredAccountingItems.filter((item) => item.entry_type === accountingTypeFilter);
  }, [dateFilteredAccountingItems, accountingTypeFilter]);

  const openCreateModal = () => {
    const todayIso = new Date().toISOString().slice(0, 10);
    const firstPatientId = patientOptions.length > 0 ? String(patientOptions[0].id) : '';
    setEditingId(0);
    setForm({
      ...emptyBillingEntry(),
      patient_id: firstPatientId,
      issue_date: todayIso,
      due_date: todayIso,
    });
    setIsModalOpen(true);
  };

  const closeModal = () => {
    setIsModalOpen(false);
    setEditingId(0);
    setForm(emptyBillingEntry());
  };

  const openCreateAccountingModal = () => {
    const todayIso = new Date().toISOString().slice(0, 10);
    setAccountingEditingId(0);
    setAccountingForm({
      ...emptyAccountingEntry(),
      entry_date: todayIso,
      category: ACCOUNTING_CATEGORY_OPTIONS.expense[0],
    });
    setIsAccountingModalOpen(true);
  };

  const closeAccountingModal = () => {
    setIsAccountingModalOpen(false);
    setAccountingEditingId(0);
    setAccountingForm(emptyAccountingEntry());
    if (accountingAttachmentInputRef.current) {
      accountingAttachmentInputRef.current.value = '';
    }
  };

  const openEditModal = (item) => {
    setEditingId(item.id);
    setForm({
      patient_id: String(item.patient_id || ''),
      concept: item.concept || '',
      issue_date: item.issue_date || '',
      due_date: item.due_date || '',
      amount: ((Number(item.amount_cents || 0) / 100).toFixed(2)),
      status: item.status || 'pending',
      payment_method: item.payment_method || '',
      notes: item.notes || '',
      paid_at: item.paid_at || '',
      tax_name: item.tax_name || 'IPSI',
      tax_rate: String(Number(item.tax_rate || 0).toFixed(2)),
    });
    setIsModalOpen(true);
  };

  const openEditAccountingModal = (item) => {
    setAccountingEditingId(item.id);
    setAccountingForm({
      entry_date: item.entry_date || '',
      entry_type: item.entry_type || 'expense',
      category: item.category || ACCOUNTING_CATEGORY_OPTIONS.expense[0],
      concept: item.concept || '',
      amount: (Number(item.amount_cents || 0) / 100).toFixed(2),
      payment_method: item.payment_method || '',
      notes: item.notes || '',
      attachment_name: item.attachment_name || '',
      attachment_type: item.attachment_type || '',
      attachment_data_url: item.attachment_data_url || '',
      related_billing_entry_id: item.related_billing_entry_id ? String(item.related_billing_entry_id) : '',
    });
    setIsAccountingModalOpen(true);
  };

  const onAccountingAttachmentSelected = (event) => {
    const file = event.target.files && event.target.files[0];

    if (!file) {
      return;
    }

    if (!/^(application\/pdf|image\/(png|jpe?g|webp|gif))$/i.test(file.type)) {
      notify('error', 'Formato no permitido. Usa PDF, PNG, JPG, WEBP o GIF.');
      event.target.value = '';
      return;
    }

    if (file.size > 10_000_000) {
      notify('error', 'El adjunto es demasiado grande. Maximo aproximado: 10MB.');
      event.target.value = '';
      return;
    }

    const reader = new FileReader();
    reader.onload = () => {
      const attachmentDataUrl = typeof reader.result === 'string' ? reader.result : '';
      setAccountingForm((prev) => ({
        ...prev,
        attachment_name: file.name,
        attachment_type: file.type,
        attachment_data_url: attachmentDataUrl,
      }));
    };
    reader.readAsDataURL(file);
  };

  const removeAccountingAttachment = () => {
    setAccountingForm((prev) => ({
      ...prev,
      attachment_name: '',
      attachment_type: '',
      attachment_data_url: '',
    }));
    if (accountingAttachmentInputRef.current) {
      accountingAttachmentInputRef.current.value = '';
    }
  };

  const submit = async (event) => {
    event.preventDefault();

    if (!form.patient_id || !form.concept.trim() || !form.issue_date || Number(form.amount || 0) <= 0) {
      notify('error', 'Niño, concepto, fecha de emision e importe son obligatorios.');
      return;
    }

    try {
      await api(editingId ? `/billing/entries/${editingId}` : '/billing/entries', {
        method: editingId ? 'PUT' : 'POST',
        body: {
          patient_id: Number(form.patient_id || 0),
          concept: form.concept,
          issue_date: form.issue_date,
          due_date: form.due_date,
          amount: form.amount,
          status: form.status,
          payment_method: form.payment_method,
          notes: form.notes,
          paid_at: form.paid_at,
          tax_name: form.tax_name,
          tax_rate: form.tax_rate,
        },
      });
      notify('success', editingId ? 'Factura actualizada.' : 'Factura creada.');
      closeModal();
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo guardar la factura.');
    }
  };

  const remove = async () => {
    if (!entryToDelete) {
      return;
    }

    try {
      await api(`/billing/entries/${entryToDelete.id}`, { method: 'DELETE' });
      notify('success', 'Factura eliminada.');
      setEntryToDelete(null);
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar la factura.');
    }
  };

  const submitAccounting = async (event) => {
    event.preventDefault();

    if (!accountingForm.entry_date || !accountingForm.category || !accountingForm.concept.trim() || Number(accountingForm.amount || 0) <= 0) {
      notify('error', 'Fecha, categoria, concepto e importe son obligatorios.');
      return;
    }

    try {
      await api(accountingEditingId ? `/billing/accounting/${accountingEditingId}` : '/billing/accounting', {
        method: accountingEditingId ? 'PUT' : 'POST',
        body: {
          entry_date: accountingForm.entry_date,
          entry_type: accountingForm.entry_type,
          category: accountingForm.category,
          concept: accountingForm.concept,
          amount: accountingForm.amount,
          payment_method: accountingForm.payment_method,
          notes: accountingForm.notes,
          attachment_name: accountingForm.attachment_name,
          attachment_type: accountingForm.attachment_type,
          attachment_data_url: accountingForm.attachment_data_url,
          related_billing_entry_id: Number(accountingForm.related_billing_entry_id || 0) || null,
        },
      });
      notify('success', accountingEditingId ? 'Movimiento actualizado.' : 'Movimiento registrado.');
      closeAccountingModal();
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo guardar el movimiento.');
    }
  };

  const removeAccounting = async () => {
    if (!accountingToDelete) {
      return;
    }

    try {
      await api(`/billing/accounting/${accountingToDelete.id}`, { method: 'DELETE' });
      notify('success', 'Movimiento eliminado.');
      setAccountingToDelete(null);
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar el movimiento.');
    }
  };

  const getStatusLabel = (status) => {
    if (status === 'paid') {
      return 'Pagada';
    }
    if (status === 'overdue') {
      return 'Vencida';
    }
    if (status === 'cancelled') {
      return 'Anulada';
    }
    return 'Pendiente';
  };

  const getTaxLabel = (item) => {
    const taxName = String(item.tax_name || '').trim();
    const taxRate = Number(item.tax_rate || 0);

    if (!taxName) {
      return 'Sin impuesto';
    }

    if (taxRate <= 0) {
      return taxName;
    }

    return `${taxName} ${taxRate.toFixed(2)}%`;
  };

  const getAccountingTypeLabel = (entryType) => {
    return entryType === 'income' ? 'Ingreso' : 'Gasto';
  };

  const downloadAccountingAttachment = (item) => {
    if (!item || !item.attachment_data_url) {
      return;
    }

    const link = document.createElement('a');
    link.href = item.attachment_data_url;
    link.download = item.attachment_name || 'adjunto';
    link.target = '_blank';
    link.rel = 'noopener';
    document.body.appendChild(link);
    link.click();
    link.remove();
  };

  const printEntry = (item) => {
    const amounts = calculateBillingAmounts(item.amount_cents, item.tax_rate);
    const invoiceNumber = `${orgSettings.invoicePrefix || 'FAC-'}${String(item.id).padStart(4, '0')}`;
    const logoUrl = `${window.location.origin}/logo/logo-sin-fondo.webp`;
    const printWindow = window.open('', '_blank', 'width=960,height=720');

    if (!printWindow) {
      notify('error', 'No se pudo abrir la ventana de impresion.');
      return;
    }

    const html = `
      <!DOCTYPE html>
      <html lang="es">
      <head>
        <meta charset="UTF-8" />
        <title></title>
        <style>
          body { font-family: Arial, sans-serif; margin: 32px; color: #1f2d45; }
          h1, h2, h3, p { margin: 0; }
          .sheet { display: grid; gap: 24px; }
          .head { display: flex; justify-content: space-between; gap: 24px; align-items: flex-start; }
          .brand { display: grid; gap: 10px; margin-top: -22px; }
          .brand-logo { width: 180px; height: auto; object-fit: contain; }
          .brand h1 { font-size: 28px; margin-bottom: 8px; }
          .brand p, .meta p { line-height: 1.5; }
          .card { border: 1px solid #d6deea; border-radius: 12px; padding: 16px; }
          .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
          table { width: 100%; border-collapse: collapse; }
          th, td { padding: 12px; border-bottom: 1px solid #d6deea; text-align: left; }
          th { background: #f4f7fb; }
          .totals { margin-left: auto; width: 320px; }
          .totals td:last-child, .totals th:last-child { text-align: right; }
          .note { color: #5a6983; font-size: 13px; line-height: 1.5; }
          @media print { body { margin: 16px; } }
        </style>
        <script>
          window.addEventListener('load', function () {
            var printed = false;
            var triggerPrint = function () {
              if (printed) {
                return;
              }
              printed = true;
              setTimeout(function () {
                window.focus();
                window.print();
              }, 120);
            };

            var logo = document.querySelector('.brand-logo');
            if (!logo) {
              triggerPrint();
              return;
            }

            if (logo.complete) {
              triggerPrint();
              return;
            }

            logo.addEventListener('load', triggerPrint, { once: true });
            logo.addEventListener('error', triggerPrint, { once: true });
            setTimeout(triggerPrint, 1200);
          });
        </script>
      </head>
      <body>
        <div class="sheet">
          <section class="head">
            <div class="brand">
              <img class="brand-logo" src="${escapeHtml(logoUrl)}" alt="AutismoCeuta" />
              <p>Documento de facturacion</p>
              <p><strong>${escapeHtml(orgSettings.legalName || 'AutismoCeuta')}</strong></p>
              <p><strong>CIF:</strong> ${escapeHtml(orgSettings.taxId || '')}</p>
            </div>
            <div class="meta card">
              <p><strong>Factura:</strong> ${escapeHtml(invoiceNumber)}</p>
              <p><strong>Emision:</strong> ${escapeHtml(formatDate(item.issue_date))}</p>
              <p><strong>Vencimiento:</strong> ${escapeHtml(formatDate(item.due_date))}</p>
              <p><strong>Estado:</strong> ${escapeHtml(getStatusLabel(getEffectiveStatus(item)))}</p>
            </div>
          </section>

          <section class="grid">
            <div class="card">
              <h3>Facturar a</h3>
              <p>${escapeHtml(item.guardian_name || '-')}</p>
              <p>${escapeHtml(item.patient_name || '-')}</p>
              <p>${escapeHtml(item.guardian_phone || '-')}</p>
            </div>
            <div class="card">
              <h3>Detalle</h3>
              <p><strong>Concepto:</strong> ${escapeHtml(item.concept || '-')}</p>
              <p><strong>Forma de pago:</strong> ${escapeHtml(item.payment_method || '-')}</p>
              <p><strong>Impuesto:</strong> ${escapeHtml(getTaxLabel(item))}</p>
            </div>
          </section>

          <section class="card">
            <table>
              <thead>
                <tr>
                  <th>Concepto</th>
                  <th>Base</th>
                  <th>Impuesto</th>
                  <th>Total</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>${escapeHtml(item.concept || '-')}</td>
                  <td>${escapeHtml(formatCurrency(amounts.baseAmount))}</td>
                  <td>${escapeHtml(formatCurrency(amounts.taxAmount))}</td>
                  <td>${escapeHtml(formatCurrency(amounts.totalAmount))}</td>
                </tr>
              </tbody>
            </table>
          </section>

          <table class="totals">
            <tbody>
              <tr>
                <th>Base imponible</th>
                <td>${escapeHtml(formatCurrency(amounts.baseAmount))}</td>
              </tr>
              <tr>
                <th>${escapeHtml(getTaxLabel(item))}</th>
                <td>${escapeHtml(formatCurrency(amounts.taxAmount))}</td>
              </tr>
              <tr>
                <th>Total</th>
                <td><strong>${escapeHtml(formatCurrency(amounts.totalAmount))}</strong></td>
              </tr>
            </tbody>
          </table>

          ${item.notes ? `<p class="note">${escapeHtml(item.notes)}</p>` : ''}
        </div>
      </body>
      </html>
    `;

    printWindow.document.open();
    printWindow.document.write(html);
    printWindow.document.close();
  };

  return (
    <section className="list-card">
      <div className="section-head">
        <div>
          <h2>Facturacion</h2>
          <p>
            Estado actual: <strong>{enabled ? 'Activa' : 'Desactivada'}</strong>
          </p>
          <p className="billing-tax-note">Ceuta: no se aplica IVA peninsular por defecto. La factura permite indicar IPSI u otra situacion fiscal segun el servicio.</p>
        </div>
        <button type="button" className="button button-primary" onClick={openCreateModal}>
          Nueva factura
        </button>
      </div>

      <div className="billing-summary-grid">
        <article className="billing-summary-card">
          <small>Total facturado</small>
          <strong>{formatCurrency(summary.total)}</strong>
        </article>
        <article className="billing-summary-card">
          <small>Pendiente</small>
          <strong>{formatCurrency(summary.pending)}</strong>
        </article>
        <article className="billing-summary-card">
          <small>Cobrado</small>
          <strong>{formatCurrency(summary.paid)}</strong>
        </article>
        <article className="billing-summary-card billing-summary-card-alert">
          <small>Vencido</small>
          <strong>{formatCurrency(summary.overdue)}</strong>
        </article>
      </div>

      <div className="documents-filters billing-filters">
        <label className="documents-filter">
          <span className="sr-only">Filtrar por estado</span>
          <select value={statusFilter} onChange={(event) => setStatusFilter(event.target.value)}>
            <option value="all">Todos los estados</option>
            <option value="pending">Pendiente</option>
            <option value="paid">Pagada</option>
            <option value="overdue">Vencida</option>
            <option value="cancelled">Anulada</option>
          </select>
        </label>
        <label className="documents-filter">
          <span className="sr-only">Filtrar por año</span>
          <select value={billingYearFilter} onChange={(event) => setBillingYearFilter(event.target.value)}>
            <option value="all">Todos los años</option>
            {billingYearOptions.map((year) => (
              <option key={year} value={year}>{year}</option>
            ))}
          </select>
        </label>
        <label className="documents-filter">
          <span className="sr-only">Filtrar por mes</span>
          <select value={billingMonthFilter} onChange={(event) => setBillingMonthFilter(event.target.value)}>
            <option value="all">Todos los meses</option>
            {MONTH_OPTIONS.map((month) => (
              <option key={month.value} value={month.value}>{month.label}</option>
            ))}
          </select>
        </label>
        <label className="documents-filter">
          <span className="sr-only">Filtrar por trimestre</span>
          <select value={billingQuarterFilter} onChange={(event) => setBillingQuarterFilter(event.target.value)}>
            <option value="all">Todos los trimestres</option>
            {QUARTER_OPTIONS.map((quarter) => (
              <option key={quarter.value} value={quarter.value}>{quarter.label}</option>
            ))}
          </select>
        </label>
        <label className="documents-filter">
          <span className="sr-only">Filtrar por semestre</span>
          <select value={billingSemesterFilter} onChange={(event) => setBillingSemesterFilter(event.target.value)}>
            <option value="all">Todos los semestres</option>
            {SEMESTER_OPTIONS.map((semester) => (
              <option key={semester.value} value={semester.value}>{semester.label}</option>
            ))}
          </select>
        </label>
      </div>

      <SimpleTable
        columns={['Niño', 'Tutor', 'Concepto', 'Base', 'Impuesto', 'Total', 'Estado', 'Acciones']}
        rows={visibleItems.map((item) => [
          item.patient_name || '-',
          item.guardian_name || '-',
          item.concept || '-',
          formatCurrency(calculateBillingAmounts(item.amount_cents, item.tax_rate).baseAmount),
          getTaxLabel(item),
          formatCurrency(calculateBillingAmounts(item.amount_cents, item.tax_rate).totalAmount),
          <span key={`status-${item.id}`} className={`billing-status-badge billing-status-${getEffectiveStatus(item)}`}>
            {getStatusLabel(getEffectiveStatus(item))}
          </span>,
          <div className="action-row" key={`billing-actions-${item.id}`}>
            <IconActionButton icon="print" label="Imprimir" onClick={() => printEntry(item)} />
            <IconActionButton icon="edit" label="Editar" onClick={() => openEditModal(item)} />
            <IconActionButton icon="delete" label="Eliminar" tone="danger" onClick={() => setEntryToDelete(item)} />
          </div>,
        ])}
      />

      {isModalOpen ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={editingId ? 'Editar factura' : 'Nueva factura'}>
          <div className="modal-card modal-card-wide">
            <div className="modal-head">
              <h3>{editingId ? 'Editar factura' : 'Nueva factura'}</h3>
              <button type="button" className="modal-close" onClick={closeModal} aria-label="Cerrar">
                X
              </button>
            </div>

            <form className="stack grid-2" onSubmit={submit}>
              <label>
                Niño
                <select value={form.patient_id} onChange={(event) => setForm((prev) => ({ ...prev, patient_id: event.target.value }))} required>
                  <option value="">Seleccionar</option>
                  {patientOptions.map((patient) => (
                    <option key={patient.id} value={patient.id}>{patient.label}</option>
                  ))}
                </select>
              </label>

              <label>
                Estado
                <select value={form.status} onChange={(event) => setForm((prev) => ({ ...prev, status: event.target.value }))}>
                  <option value="pending">Pendiente</option>
                  <option value="paid">Pagada</option>
                  <option value="overdue">Vencida</option>
                  <option value="cancelled">Anulada</option>
                </select>
              </label>

              <label className="span-2">
                Concepto
                <input value={form.concept} onChange={(event) => setForm((prev) => ({ ...prev, concept: event.target.value }))} required />
              </label>

              <label>
                Fecha de emision
                <div className="localized-date-field">
                  <input ref={issueDateInputRef} type="text" placeholder="DD/MM/AAAA" required />
                  <button type="button" className="localized-date-trigger" aria-label="Abrir calendario" onClick={openIssueDatePicker}>
                    <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                      <path d="M7 2h2v2h6V2h2v2h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h3V2zm13 8H4v10h16V10zM4 8h16V6H4v2z" />
                    </svg>
                  </button>
                </div>
              </label>

              <label>
                Fecha de vencimiento
                <div className="localized-date-field">
                  <input ref={dueDateInputRef} type="text" placeholder="DD/MM/AAAA" />
                  <button type="button" className="localized-date-trigger" aria-label="Abrir calendario" onClick={openDueDatePicker}>
                    <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                      <path d="M7 2h2v2h6V2h2v2h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h3V2zm13 8H4v10h16V10zM4 8h16V6H4v2z" />
                    </svg>
                  </button>
                </div>
              </label>

              <label>
                Importe
                <input type="number" min="0" step="0.01" value={form.amount} onChange={(event) => setForm((prev) => ({ ...prev, amount: event.target.value }))} required />
              </label>

              <label>
                Tipo de impuesto
                <TaxTypeSelect
                  taxName={form.tax_name}
                  taxRate={form.tax_rate}
                  onSelect={(nextTaxName, nextTaxRate) =>
                    setForm((prev) => ({ ...prev, tax_name: nextTaxName, tax_rate: nextTaxRate }))
                  }
                />
              </label>

              <label>
                Tipo %
                <input type="number" min="0" step="0.01" value={form.tax_rate} onChange={(event) => setForm((prev) => ({ ...prev, tax_rate: event.target.value }))} />
              </label>

              <label>
                Forma de pago
                <select value={form.payment_method} onChange={(event) => setForm((prev) => ({ ...prev, payment_method: event.target.value }))}>
                  <option value="">Seleccionar</option>
                  <option value="transferencia">Transferencia</option>
                  <option value="efectivo">Efectivo</option>
                  <option value="tarjeta">Tarjeta</option>
                  <option value="domiciliacion">Domiciliacion</option>
                </select>
              </label>

              <label className="span-2">
                Observaciones
                <textarea value={form.notes} onChange={(event) => setForm((prev) => ({ ...prev, notes: event.target.value }))} />
              </label>

              <div className="inline-actions span-2 session-modal-actions">
                <button type="button" className="button button-ghost" onClick={closeModal}>
                  Cancelar
                </button>
                <button className="button button-primary" type="submit">
                  {editingId ? 'Guardar cambios' : 'Guardar factura'}
                </button>
              </div>
            </form>
          </div>
        </div>
      ) : null}

      <ConfirmActionModal
        open={Boolean(entryToDelete)}
        title="Eliminar factura"
        message={entryToDelete ? `Vas a eliminar la factura \"${entryToDelete.concept}\".` : ''}
        onCancel={() => setEntryToDelete(null)}
        onConfirm={remove}
        confirmLabel="Eliminar"
      />

      <div className="billing-section-divider" />

      <div className="section-head billing-ledger-header">
        <div>
          <h2>Contabilidad</h2>
          <p className="billing-inline-note">Libro simplificado de ingresos y gastos de la asociacion.</p>
        </div>
        <button type="button" className="button button-primary" onClick={openCreateAccountingModal}>
          Nuevo movimiento
        </button>
      </div>

      <div className="billing-summary-grid">
        <article className="billing-summary-card billing-summary-card-income">
          <small>Ingresos</small>
          <strong>{formatCurrency(accountingSummary.income)}</strong>
        </article>
        <article className="billing-summary-card billing-summary-card-expense">
          <small>Gastos</small>
          <strong>{formatCurrency(accountingSummary.expense)}</strong>
        </article>
        <article className={`billing-summary-card ${accountingSummary.balance >= 0 ? '' : 'billing-summary-card-alert'}`}>
          <small>Saldo</small>
          <strong>{formatCurrency(accountingSummary.balance)}</strong>
        </article>
      </div>

      <div className="documents-filters billing-filters">
        <label className="documents-filter">
          <span className="sr-only">Filtrar movimientos</span>
          <select value={accountingTypeFilter} onChange={(event) => setAccountingTypeFilter(event.target.value)}>
            <option value="all">Todos los movimientos</option>
            <option value="income">Ingresos</option>
            <option value="expense">Gastos</option>
          </select>
        </label>
        <label className="documents-filter">
          <span className="sr-only">Filtrar por año</span>
          <select value={accountingYearFilter} onChange={(event) => setAccountingYearFilter(event.target.value)}>
            <option value="all">Todos los años</option>
            {accountingYearOptions.map((year) => (
              <option key={year} value={year}>{year}</option>
            ))}
          </select>
        </label>
        <label className="documents-filter">
          <span className="sr-only">Filtrar por mes</span>
          <select value={accountingMonthFilter} onChange={(event) => setAccountingMonthFilter(event.target.value)}>
            <option value="all">Todos los meses</option>
            {MONTH_OPTIONS.map((month) => (
              <option key={month.value} value={month.value}>{month.label}</option>
            ))}
          </select>
        </label>
        <label className="documents-filter">
          <span className="sr-only">Filtrar por trimestre</span>
          <select value={accountingQuarterFilter} onChange={(event) => setAccountingQuarterFilter(event.target.value)}>
            <option value="all">Todos los trimestres</option>
            {QUARTER_OPTIONS.map((quarter) => (
              <option key={quarter.value} value={quarter.value}>{quarter.label}</option>
            ))}
          </select>
        </label>
        <label className="documents-filter">
          <span className="sr-only">Filtrar por semestre</span>
          <select value={accountingSemesterFilter} onChange={(event) => setAccountingSemesterFilter(event.target.value)}>
            <option value="all">Todos los semestres</option>
            {SEMESTER_OPTIONS.map((semester) => (
              <option key={semester.value} value={semester.value}>{semester.label}</option>
            ))}
          </select>
        </label>
      </div>

      <SimpleTable
        columns={['Fecha', 'Tipo', 'Categoria', 'Concepto', 'Factura vinculada', 'Adjunto', 'Medio', 'Importe', 'Acciones']}
        rows={visibleAccountingItems.map((item) => [
          formatDate(item.entry_date),
          <span key={`type-${item.id}`} className={`billing-entry-type-badge billing-entry-type-${item.entry_type}`}>
            {getAccountingTypeLabel(item.entry_type)}
          </span>,
          item.category || '-',
          item.concept || '-',
          item.related_billing_entry_id ? `${orgSettings.invoicePrefix || 'FAC-'}${String(item.related_billing_entry_id).padStart(4, '0')} - ${item.related_patient_name || item.related_billing_concept || ''}` : '-',
          item.attachment_data_url ? (
            <button type="button" className="button button-ghost billing-attachment-button" onClick={() => downloadAccountingAttachment(item)}>
              {item.attachment_name || 'Descargar adjunto'}
            </button>
          ) : '-',
          item.payment_method || '-',
          formatCurrency(Number(item.amount_cents || 0) / 100),
          <div className="action-row" key={`accounting-actions-${item.id}`}>
            <IconActionButton icon="edit" label="Editar" onClick={() => openEditAccountingModal(item)} />
            <IconActionButton icon="delete" label="Eliminar" tone="danger" onClick={() => setAccountingToDelete(item)} />
          </div>,
        ])}
      />

      {isAccountingModalOpen ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={accountingEditingId ? 'Editar movimiento' : 'Nuevo movimiento'}>
          <div className="modal-card modal-card-wide">
            <div className="modal-head">
              <h3>{accountingEditingId ? 'Editar movimiento contable' : 'Nuevo movimiento contable'}</h3>
              <button type="button" className="modal-close" onClick={closeAccountingModal} aria-label="Cerrar">
                X
              </button>
            </div>

            <form className="stack grid-2" onSubmit={submitAccounting}>
              <label>
                Fecha
                <div className="localized-date-field">
                  <input ref={entryDateInputRef} type="text" placeholder="DD/MM/AAAA" required />
                  <button type="button" className="localized-date-trigger" aria-label="Abrir calendario" onClick={openEntryDatePicker}>
                    <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                      <path d="M7 2h2v2h6V2h2v2h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h3V2zm13 8H4v10h16V10zM4 8h16V6H4v2z" />
                    </svg>
                  </button>
                </div>
              </label>

              <label>
                Tipo
                <select
                  value={accountingForm.entry_type}
                  onChange={(event) => {
                    const nextType = event.target.value;
                    setAccountingForm((prev) => ({
                      ...prev,
                      entry_type: nextType,
                      category: ACCOUNTING_CATEGORY_OPTIONS[nextType][0],
                    }));
                  }}
                >
                  <option value="income">Ingreso</option>
                  <option value="expense">Gasto</option>
                </select>
              </label>

              <label>
                Categoria
                <select value={accountingForm.category} onChange={(event) => setAccountingForm((prev) => ({ ...prev, category: event.target.value }))} required>
                  {ACCOUNTING_CATEGORY_OPTIONS[accountingForm.entry_type].map((option) => (
                    <option key={option} value={option}>{option}</option>
                  ))}
                </select>
              </label>

              <label>
                Factura vinculada
                <select value={accountingForm.related_billing_entry_id} onChange={(event) => setAccountingForm((prev) => ({ ...prev, related_billing_entry_id: event.target.value }))}>
                  <option value="">Sin vincular</option>
                  {billingOptions.map((option) => (
                    <option key={option.id} value={option.id}>{option.label}</option>
                  ))}
                </select>
              </label>

              <label className="span-2">
                Concepto
                <input value={accountingForm.concept} onChange={(event) => setAccountingForm((prev) => ({ ...prev, concept: event.target.value }))} required />
              </label>

              <label>
                Importe
                <input type="number" min="0" step="0.01" value={accountingForm.amount} onChange={(event) => setAccountingForm((prev) => ({ ...prev, amount: event.target.value }))} required />
              </label>

              <label>
                Medio de pago
                <select value={accountingForm.payment_method} onChange={(event) => setAccountingForm((prev) => ({ ...prev, payment_method: event.target.value }))}>
                  <option value="">Seleccionar</option>
                  <option value="transferencia">Transferencia</option>
                  <option value="efectivo">Efectivo</option>
                  <option value="tarjeta">Tarjeta</option>
                  <option value="domiciliacion">Domiciliacion</option>
                </select>
              </label>

              <label className="span-2">
                Adjunto
                <input
                  ref={accountingAttachmentInputRef}
                  type="file"
                  accept="application/pdf,image/png,image/jpeg,image/webp,image/gif"
                  onChange={onAccountingAttachmentSelected}
                />
              </label>

              {accountingForm.attachment_name ? (
                <div className="span-2 billing-attachment-row">
                  <strong>{accountingForm.attachment_name}</strong>
                  <button type="button" className="button button-ghost" onClick={removeAccountingAttachment}>
                    Quitar adjunto
                  </button>
                </div>
              ) : null}

              <label className="span-2">
                Notas
                <textarea value={accountingForm.notes} onChange={(event) => setAccountingForm((prev) => ({ ...prev, notes: event.target.value }))} />
              </label>

              <div className="inline-actions span-2 session-modal-actions">
                <button type="button" className="button button-ghost" onClick={closeAccountingModal}>
                  Cancelar
                </button>
                <button className="button button-primary" type="submit">
                  {accountingEditingId ? 'Guardar cambios' : 'Guardar movimiento'}
                </button>
              </div>
            </form>
          </div>
        </div>
      ) : null}

      <ConfirmActionModal
        open={Boolean(accountingToDelete)}
        title="Eliminar movimiento"
        message={accountingToDelete ? `Vas a eliminar el movimiento \"${accountingToDelete.concept}\".` : ''}
        onCancel={() => setAccountingToDelete(null)}
        onConfirm={removeAccounting}
        confirmLabel="Eliminar"
      />
    </section>
  );
}

function ProfilePage() {
  const { notify, refreshSession } = useApp();
  const [profile, setProfile] = useState(null);
  const [photoDraft, setPhotoDraft] = useState('');
  const [savingPhoto, setSavingPhoto] = useState(false);

  useEffect(() => {
    api('/profile')
      .then((data) => {
        setProfile(data.profile);
        setPhotoDraft(data.profile.profilePhoto || '');
      })
      .catch((error) => notify('error', error.message || 'No se pudo cargar el perfil.'));
  }, [notify]);

  const submitProfilePhoto = async (photoDataUrl) => {
    const response = await api('/profile/photo', {
      method: 'POST',
      body: { photoDataUrl: photoDataUrl || '' },
    });

    setProfile(response.profile);
    setPhotoDraft(response.profile.profilePhoto || '');
    await refreshSession();
  };

  const onPhotoSelected = (event) => {
    const file = event.target.files && event.target.files[0];

    if (!file) {
      return;
    }

    if (!/^image\/(png|jpe?g|webp|gif)$/i.test(file.type)) {
      notify('error', 'Formato no permitido. Usa PNG, JPG, WEBP o GIF.');
      event.target.value = '';
      return;
    }

    if (file.size > 1_500_000) {
      notify('error', 'La imagen es demasiado grande. Maximo: 1.5MB.');
      event.target.value = '';
      return;
    }

    const reader = new FileReader();
    reader.onload = async () => {
      const photoDataUrl = typeof reader.result === 'string' ? reader.result : '';
      setPhotoDraft(photoDataUrl);
      setSavingPhoto(true);

      try {
        await submitProfilePhoto(photoDataUrl);
        notify('success', 'Foto de perfil actualizada.');
      } catch (error) {
        notify('error', error.message || 'No se pudo guardar la foto de perfil.');
      } finally {
        setSavingPhoto(false);
      }
    };
    reader.readAsDataURL(file);
  };

  const savePhoto = async () => {
    setSavingPhoto(true);

    try {
      await submitProfilePhoto(photoDraft || '');
      notify('success', 'Foto de perfil actualizada.');
    } catch (error) {
      notify('error', error.message || 'No se pudo guardar la foto de perfil.');
    } finally {
      setSavingPhoto(false);
    }
  };

  const removePhoto = async () => {
    setSavingPhoto(true);

    try {
      await submitProfilePhoto('');
      setPhotoDraft('');
      notify('success', 'Foto de perfil eliminada.');
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar la foto de perfil.');
    } finally {
      setSavingPhoto(false);
    }
  };

  if (!profile) {
    return <section className="list-card">Cargando perfil...</section>;
  }

  return (
    <section className="list-card">
      <h2>Mi perfil</h2>
      <div className="profile-photo-editor">
        <div className="profile-photo-preview">
          {photoDraft ? (
            <img src={photoDraft} alt="Foto de perfil" className="profile-photo-preview-img" />
          ) : (
            <span className="profile-photo-placeholder">Sin foto</span>
          )}
        </div>
        <div className="profile-photo-actions">
          <label className="button button-ghost profile-photo-upload">
            Subir foto
            <input type="file" accept="image/png,image/jpeg,image/webp,image/gif" onChange={onPhotoSelected} />
          </label>
          <button type="button" className="button button-primary" onClick={savePhoto} disabled={savingPhoto}>
            {savingPhoto ? 'Guardando...' : 'Guardar foto'}
          </button>
          {profile.profilePhoto ? (
            <button type="button" className="button button-danger" onClick={removePhoto} disabled={savingPhoto}>
              Quitar foto
            </button>
          ) : null}
        </div>
      </div>
      <p>
        <strong>Nombre:</strong> {profile.name}
      </p>
      <p>
        <strong>Usuario:</strong> {profile.username}
      </p>
      <p>
        <strong>Rol:</strong> {profile.role}
      </p>
      <p>
        <strong>Especialidad:</strong> {profile.specialty || '-'}
      </p>
    </section>
  );
}

function SessionsPage() {
  const { notify, session } = useApp();
  const [items, setItems] = useState([]);
  const [patients, setPatients] = useState([]);
  const [therapies, setTherapies] = useState([]);
  const [therapists, setTherapists] = useState([]);
  const [selectedPatientId, setSelectedPatientId] = useState(0);
  const [selectedYear, setSelectedYear] = useState('all');
  const [selectedMonth, setSelectedMonth] = useState('all');
  const [showCompleted, setShowCompleted] = useState(false);
  const [previewSession, setPreviewSession] = useState(null);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [sessionToDelete, setSessionToDelete] = useState(null);
  const [editingId, setEditingId] = useState(0);
  const [completionModalSession, setCompletionModalSession] = useState(null);
  const [completionModalResult, setCompletionModalResult] = useState(null);
  const [completionModalNotes, setCompletionModalNotes] = useState('');
  const sessionDateInputRef = useRef(null);
  const sessionDatePickerRef = useRef(null);
  const sessionStartTimeInputRef = useRef(null);
  const sessionEndTimeInputRef = useRef(null);
  const sessionStartTimePickerRef = useRef(null);
  const sessionEndTimePickerRef = useRef(null);
  const [form, setForm] = useState({ patient_id: '', therapy_id: '', therapist_id: '', session_date: '', session_start_time: '', session_end_time: '', notes: '' });

  const load = useCallback(async () => {
    try {
      const data = await api('/sessions');
      setItems(Array.isArray(data.items) ? data.items : []);
      setPatients(Array.isArray(data.patients) ? data.patients : []);
      setTherapies(Array.isArray(data.therapies) ? data.therapies : []);
      setTherapists(Array.isArray(data.therapists) ? data.therapists : []);
    } catch (error) {
      notify('error', error.message || 'No se pudieron cargar las sesiones.');
    }
  }, [notify]);

  useEffect(() => {
    load();
  }, [load]);

  const getSessionYear = useCallback((sessionItem) => {
    const rawDate = sessionItem.session_date;
    if (!rawDate) {
      return 'Sin ano';
    }

    const parsed = new Date(rawDate);
    if (Number.isNaN(parsed.getTime())) {
      return 'Sin ano';
    }

    return String(parsed.getFullYear());
  }, []);

  const getSessionMonth = useCallback((sessionItem) => {
    const rawDate = sessionItem.session_date;
    if (!rawDate) {
      return 'all';
    }

    const parsed = new Date(rawDate);
    if (Number.isNaN(parsed.getTime())) {
      return 'all';
    }

    return String(parsed.getMonth() + 1).padStart(2, '0');
  }, []);

  const yearOptions = useMemo(() => {
    const years = Array.from(new Set(items.map((sessionItem) => getSessionYear(sessionItem))));
    return years.sort((yearA, yearB) => {
      if (yearA === 'Sin ano') {
        return 1;
      }
      if (yearB === 'Sin ano') {
        return -1;
      }
      return Number(yearB) - Number(yearA);
    });
  }, [items, getSessionYear]);

  const visibleSessions = useMemo(() => {
    return items.filter((sessionItem) => {
      const matchesPatient = !selectedPatientId || Number(sessionItem.patient_id) === Number(selectedPatientId);
      const matchesYear = selectedYear === 'all' || getSessionYear(sessionItem) === selectedYear;
      const matchesMonth = selectedMonth === 'all' || getSessionMonth(sessionItem) === selectedMonth;
      const matchesStatus = showCompleted || !sessionItem.is_completed;
      return matchesPatient && matchesYear && matchesMonth && matchesStatus;
    });
  }, [items, selectedPatientId, selectedYear, selectedMonth, getSessionYear, getSessionMonth, showCompleted]);

  const getSessionStatus = useCallback((sessionItem) => {
    if (!sessionItem.is_completed) {
      return { label: 'Pendiente', className: 'document-type-pill-warning' };
    }
    if (sessionItem.completion_result === 'problem') {
      return { label: 'Con incidencia', className: 'document-type-pill-danger' };
    }
    return { label: 'Completada', className: 'document-type-pill-success' };
  }, []);

  const groupedSessions = useMemo(() => {
    const groups = new Map();

    visibleSessions.forEach((sessionItem) => {
      const year = getSessionYear(sessionItem);
      if (!groups.has(year)) {
        groups.set(year, []);
      }
      groups.get(year).push(sessionItem);
    });

    return Array.from(groups.entries()).sort(([yearA], [yearB]) => {
      if (yearA === 'Sin ano') {
        return 1;
      }
      if (yearB === 'Sin ano') {
        return -1;
      }
      return Number(yearB) - Number(yearA);
    });
  }, [visibleSessions, getSessionYear]);

  useEffect(() => {
    if (selectedYear === 'all') {
      return;
    }

    if (!yearOptions.includes(selectedYear)) {
      setSelectedYear('all');
    }
  }, [yearOptions, selectedYear]);

  const patientOptions = useMemo(() => {
    return patients.map((patient) => ({
      id: patient.id,
      label: `${patient.first_name || ''} ${patient.last_name || ''}`.trim(),
    }));
  }, [patients]);

  useEffect(() => {
    if (!isModalOpen) {
      return undefined;
    }

    const input = sessionDateInputRef.current;
    if (!input || !window.flatpickr) {
      return undefined;
    }

    const picker = window.flatpickr(input, {
      locale: (window.flatpickr.l10ns && window.flatpickr.l10ns.es) || 'default',
      dateFormat: 'Y-m-d',
      altInput: true,
      altFormat: 'd/m/Y',
      defaultDate: form.session_date || null,
      allowInput: false,
      disableMobile: true,
      onChange: (selectedDates) => {
        if (!selectedDates.length) {
          setForm((prev) => ({ ...prev, session_date: '' }));
          return;
        }

        const selectedDate = selectedDates[0];
        const normalized = `${selectedDate.getFullYear()}-${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}`;
        setForm((prev) => ({ ...prev, session_date: normalized }));
      },
    });

    const startTimePicker = window.flatpickr(sessionStartTimeInputRef.current, {
      locale: (window.flatpickr.l10ns && window.flatpickr.l10ns.es) || 'default',
      enableTime: true,
      noCalendar: true,
      dateFormat: 'H:i',
      time_24hr: true,
      defaultDate: form.session_start_time || '09:00',
      allowInput: false,
      disableMobile: true,
      onChange: (selectedDates, value) => {
        setForm((prev) => ({ ...prev, session_start_time: value }));
      },
    });

    const endTimePicker = window.flatpickr(sessionEndTimeInputRef.current, {
      locale: (window.flatpickr.l10ns && window.flatpickr.l10ns.es) || 'default',
      enableTime: true,
      noCalendar: true,
      dateFormat: 'H:i',
      time_24hr: true,
      defaultDate: form.session_end_time || '10:00',
      allowInput: false,
      disableMobile: true,
      onChange: (selectedDates, value) => {
        setForm((prev) => ({ ...prev, session_end_time: value }));
      },
    });

    sessionDatePickerRef.current = picker;
    sessionStartTimePickerRef.current = startTimePicker;
    sessionEndTimePickerRef.current = endTimePicker;

    return () => {
      picker.destroy();
      startTimePicker.destroy();
      endTimePicker.destroy();
      sessionDatePickerRef.current = null;
      sessionStartTimePickerRef.current = null;
      sessionEndTimePickerRef.current = null;
    };
  }, [isModalOpen, editingId]);

  const openCreateModal = () => {
    const todayIso = new Date().toISOString().slice(0, 10);
    const firstPatientId = patientOptions.length > 0 ? String(patientOptions[0].id) : '';
    const defaultTherapistId = session.user.role === 'terapeuta' ? String(session.user.id) : '';
    setEditingId(0);
    setForm({
      patient_id: firstPatientId,
      therapy_id: '',
      therapist_id: defaultTherapistId,
      session_date: todayIso,
      session_start_time: '09:00',
      session_end_time: '10:00',
      notes: '',
    });
    setIsModalOpen(true);
  };

  const closeModal = () => {
    setIsModalOpen(false);
    setEditingId(0);
    setForm({ patient_id: '', therapy_id: '', therapist_id: '', session_date: '', session_start_time: '', session_end_time: '', notes: '' });
  };

  const openEditModal = (sessionItem) => {
    setEditingId(sessionItem.id);
    setForm({
      patient_id: String(sessionItem.patient_id || ''),
      therapy_id: String(sessionItem.therapy_id || ''),
      therapist_id: session.user.role === 'terapeuta' ? String(session.user.id) : String(sessionItem.therapist_id || ''),
      session_date: sessionItem.session_date || '',
      session_start_time: sessionItem.session_start_time || '09:00',
      session_end_time: sessionItem.session_end_time || '10:00',
      notes: sessionItem.notes || '',
    });
    setIsModalOpen(true);
  };

  const openDatePicker = () => {
    const picker = sessionDatePickerRef.current;
    if (picker) {
      picker.open();
      return;
    }

    const input = sessionDateInputRef.current;
    if (input) {
      input.focus();
      input.click();
    }
  };

  const openStartTimePicker = () => {
    const picker = sessionStartTimePickerRef.current;
    if (picker) {
      picker.open();
    }
  };

  const openEndTimePicker = () => {
    const picker = sessionEndTimePickerRef.current;
    if (picker) {
      picker.open();
    }
  };

  const submitSession = async (event) => {
    event.preventDefault();
    const patientId = Number(form.patient_id || 0);
    const therapyId = Number(form.therapy_id || 0);
    const therapistId = Number(form.therapist_id || 0);

    if (!patientId || !therapyId) {
      notify('error', 'Debes seleccionar niño y tipo de sesión.');
      return;
    }

    if (!therapistId) {
      notify('error', 'Debes seleccionar el terapeuta que tratará la sesión.');
      return;
    }

    const normalizedDate = normalizeSessionDate(form.session_date);
    if (!normalizedDate) {
      notify('error', 'La fecha debe tener formato DD/MM/AAAA.');
      return;
    }

    const normalizedEndTime = normalizeSessionTime(form.session_end_time);
    if (!normalizedEndTime) {
      notify('error', 'La hora fin debe tener formato 24 horas HH:MM.');
      return;
    }

    const normalizedStartTime = normalizeSessionTime(form.session_start_time);
    if (!normalizedStartTime) {
      notify('error', 'La hora inicial debe tener formato 24 horas HH:MM.');
      return;
    }

    try {
      await api(editingId ? `/sessions/${editingId}` : '/sessions', {
        method: editingId ? 'PUT' : 'POST',
        body: {
          patient_id: patientId,
          therapy_id: therapyId,
          therapist_id: therapistId,
          session_date: normalizedDate,
          session_start_time: normalizedStartTime,
          session_end_time: normalizedEndTime,
          notes: form.notes,
        },
      });
      notify('success', editingId ? 'Sesión actualizada.' : 'Sesión registrada.');
      closeModal();
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo guardar la sesión.');
    }
  };

  const openCompletionModal = (sessionItem, result, event) => {
    if (event) {
      event.stopPropagation();
    }

    if (sessionItem.is_completed) {
      return;
    }

    if (!sessionItem.therapist_id) {
      notify('error', 'Asigna un terapeuta a la sesión antes de marcarla como completada.');
      return;
    }

    setCompletionModalSession(sessionItem);
    setCompletionModalResult(result);
    setCompletionModalNotes(sessionItem.notes || '');
  };

  const closeCompletionModal = () => {
    setCompletionModalSession(null);
    setCompletionModalResult(null);
    setCompletionModalNotes('');
  };

  const confirmCompletionModal = async () => {
    if (!completionModalSession) {
      return;
    }

    try {
      const notesChanged = completionModalNotes.trim() !== String(completionModalSession.notes || '').trim();

      if (notesChanged) {
        await api(`/sessions/${completionModalSession.id}`, {
          method: 'PUT',
          body: {
            patient_id: completionModalSession.patient_id,
            therapy_id: completionModalSession.therapy_id,
            therapist_id: completionModalSession.therapist_id,
            session_date: completionModalSession.session_date,
            session_start_time: completionModalSession.session_start_time,
            session_end_time: completionModalSession.session_end_time,
            notes: completionModalNotes,
          },
        });
      }

      await api(`/sessions/${completionModalSession.id}/complete`, {
        method: 'POST',
        body: { completed: true, result: completionModalResult },
      });
      notify('success', completionModalResult === 'problem' ? 'Incidencia registrada en el historial de sesiones.' : 'Sesión marcada como completada.');
      closeCompletionModal();
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo actualizar el estado de la sesión.');
    }
  };

  const removeSession = async () => {
    if (!sessionToDelete) {
      return;
    }

    try {
      await api(`/sessions/${sessionToDelete.id}`, { method: 'DELETE' });
      notify('success', 'Sesión eliminada.');
      setSessionToDelete(null);
      await load();
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar la sesión.');
    }
  };

  const closePreviewSession = () => {
    setPreviewSession(null);
  };

  const buildSessionPreview = (sessionItem) => {
    const notes = String(sessionItem.notes || '').trim();
    if (!notes) {
      return 'Sin notas registradas.';
    }
    if (notes.length <= 96) {
      return notes;
    }
    return `${notes.slice(0, 96)}...`;
  };

  const getSessionScheduleLabel = (sessionItem) => {
    const start = formatTime(sessionItem.session_start_time);
    const end = formatTime(sessionItem.session_end_time);

    if (start === '-' && end === '-') {
      return 'Sin horario';
    }

    if (start === '-') {
      return `Hasta ${end}`;
    }

    if (end === '-') {
      return `Desde ${start}`;
    }

    return `${start} - ${end}`;
  };

  return (
    <section className="list-card documents-board">
      <div className="documents-header-row">
        <h2 className="documents-title">
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
            <path d="M7 2h2v2h6V2h2v2h3v18H4V4h3V2zm11 8H6v10h12V10zM4 8h16V6H4v2z" />
          </svg>
          <span>Sesiones</span>
        </h2>
        <button type="button" className="button button-primary" onClick={openCreateModal}>
          Añadir sesión
        </button>
      </div>
      <p>
        {session.user.role === 'terapeuta'
          ? 'Listado de sesiones de niños asignados a tu terapeuta.'
          : 'Listado general de sesiones registradas.'}
      </p>

      <div className="documents-filters sessions-filters">
        <label className="documents-filter">
          <span className="sr-only">Filtrar por niño</span>
          <select value={selectedPatientId} onChange={(e) => setSelectedPatientId(Number(e.target.value || 0))}>
            <option value={0}>Todos los niños</option>
            {patientOptions.map((patient) => (
              <option key={patient.id} value={patient.id}>{patient.label}</option>
            ))}
          </select>
        </label>
        <label className="documents-filter">
          <span className="sr-only">Filtrar por año</span>
          <select value={selectedYear} onChange={(e) => setSelectedYear(e.target.value)}>
            <option value="all">Todos los años</option>
            {yearOptions.map((year) => (
              <option key={year} value={year}>{year}</option>
            ))}
          </select>
        </label>
        <label className="documents-filter">
          <span className="sr-only">Filtrar por mes</span>
          <select value={selectedMonth} onChange={(e) => setSelectedMonth(e.target.value)}>
            <option value="all">Todos los meses</option>
            {MONTH_OPTIONS.map((month) => (
              <option key={month.value} value={month.value}>{month.label}</option>
            ))}
          </select>
        </label>
        <label className="documents-filter documents-filter-checkbox">
          <input type="checkbox" checked={showCompleted} onChange={(e) => setShowCompleted(e.target.checked)} />
          <span>Mostrar sesiones completadas</span>
        </label>
      </div>

      {groupedSessions.length === 0 ? (
        <p className="documents-empty">No hay sesiones para los filtros seleccionados.</p>
      ) : (
        groupedSessions.map(([year, sessionGroup]) => (
          <div key={year} className="documents-year-block">
            <h3 className="documents-year-title">
              <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                <path d="M7 2h2v2h6V2h2v2h3v18H4V4h3V2zm11 8H6v10h12V10z" />
              </svg>
              <span>{year}</span>
            </h3>

            <div className="documents-grid">
              {sessionGroup.map((sessionItem) => (
                <article
                  key={sessionItem.id}
                  className="document-card"
                  role="button"
                  tabIndex={0}
                  onClick={() => setPreviewSession(sessionItem)}
                  onKeyDown={(event) => {
                    if (event.key === 'Enter' || event.key === ' ') {
                      event.preventDefault();
                      setPreviewSession(sessionItem);
                    }
                  }}
                >
                  <div className="document-card-top">
                    <span className="document-type-pill document-type-pill-neutral">
                      {sessionItem.therapy_name || 'Sesión'}
                    </span>
                    <span className={`document-type-pill ${getSessionStatus(sessionItem).className}`}>
                      {getSessionStatus(sessionItem).label}
                    </span>
                  </div>

                  <h4>{sessionItem.patient_name || 'Sin niño asignado'}</h4>
                  <p className="document-card-patient">{sessionItem.therapist_name || 'Sin terapeuta asignado'}</p>
                  <p className="document-card-summary">{buildSessionPreview(sessionItem)}</p>

                  <div className="document-card-meta">
                    <span>{formatDate(sessionItem.session_date)}</span>
                    <span>{getSessionScheduleLabel(sessionItem)}</span>
                  </div>

                  <div className="document-card-actions action-row session-card-actions">
                    <div className="session-status-actions">
                      {!sessionItem.is_completed ? (
                        <>
                          <IconActionButton
                            icon="thumb-up"
                            label="Marcar completada"
                            tone="success"
                            onClick={(event) => openCompletionModal(sessionItem, 'success', event)}
                          />
                          <IconActionButton
                            icon="thumb-down"
                            label="Registrar incidencia"
                            tone="danger"
                            onClick={(event) => openCompletionModal(sessionItem, 'problem', event)}
                          />
                        </>
                      ) : null}
                    </div>
                    <div className="session-manage-actions">
                      <IconActionButton
                        icon="edit"
                        label="Editar"
                        onClick={(event) => {
                          event.stopPropagation();
                          openEditModal(sessionItem);
                        }}
                      />
                      <IconActionButton
                        icon="delete"
                        label="Eliminar"
                        tone="danger"
                        onClick={(event) => {
                          event.stopPropagation();
                          setSessionToDelete(sessionItem);
                        }}
                      />
                    </div>
                  </div>
                </article>
              ))}
            </div>
          </div>
        ))
      )}

      {previewSession ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label="Vista de sesión">
          <div className="modal-card modal-card-wide document-preview-modal">
            <div className="modal-head">
              <h3>{previewSession.patient_name || 'Sesión'}</h3>
              <button type="button" className="modal-close" onClick={closePreviewSession} aria-label="Cerrar">
                X
              </button>
            </div>

            <div className="document-preview-meta-row">
              <span className="document-type-pill document-type-pill-neutral">
                {previewSession.therapy_name || 'Sesión'}
              </span>
              <span className={`document-type-pill ${getSessionStatus(previewSession).className}`}>
                {getSessionStatus(previewSession).label}
              </span>
              <span>{formatDate(previewSession.session_date)}</span>
              <span>{getSessionScheduleLabel(previewSession)}</span>
              <span>{previewSession.therapist_name || 'Sin terapeuta asignado'}</span>
            </div>

            <div className="document-preview-body">
              <section>
                <h4>Notas</h4>
                <p>{previewSession.notes || 'Sin notas registradas.'}</p>
              </section>
            </div>
          </div>
        </div>
      ) : null}

      {isModalOpen ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={editingId ? 'Editar sesión' : 'Nueva sesión'}>
          <div className="modal-card">
            <div className="modal-head">
              <h3>{editingId ? 'Editar sesión' : 'Nueva sesión'}</h3>
              <button type="button" className="modal-close" onClick={closeModal} aria-label="Cerrar">
                X
              </button>
            </div>

            <form className="stack grid-2" onSubmit={submitSession}>
              <label>
                Niño/Paciente
                <select
                  value={form.patient_id}
                  onChange={(event) => setForm((prev) => ({ ...prev, patient_id: event.target.value }))}
                  required
                >
                  <option value="">Seleccionar</option>
                  {patientOptions.map((patient) => (
                    <option key={patient.id} value={patient.id}>{patient.label}</option>
                  ))}
                </select>
              </label>

              <label>
                Tipo de sesión
                <select
                  value={form.therapy_id}
                  onChange={(event) => setForm((prev) => ({ ...prev, therapy_id: event.target.value }))}
                  required
                >
                  <option value="">Seleccionar</option>
                  {therapies.map((therapy) => (
                    <option key={therapy.id} value={therapy.id}>{therapy.name}</option>
                  ))}
                </select>
              </label>

              <label>
                Terapeuta
                <select
                  value={form.therapist_id}
                  onChange={(event) => setForm((prev) => ({ ...prev, therapist_id: event.target.value }))}
                  disabled={session.user.role === 'terapeuta'}
                  required
                >
                  <option value="">Seleccionar</option>
                  {therapists.map((therapist) => (
                    <option key={therapist.id} value={therapist.id}>{therapist.name}</option>
                  ))}
                </select>
              </label>
              <div className="session-schedule-row span-2">
                <label>
                  Fecha de sesión
                  <div className="localized-date-field">
                    <input
                      ref={sessionDateInputRef}
                      type="text"
                      placeholder="DD/MM/AAAA"
                      required
                    />
                    <button type="button" className="localized-date-trigger" aria-label="Abrir calendario" onClick={openDatePicker}>
                      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                        <path d="M7 2h2v2h6V2h2v2h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h3V2zm13 8H4v10h16V10zM4 8h16V6H4v2z" />
                      </svg>
                    </button>
                  </div>
                </label>

                <label>
                  Hora inicial
                  <div className="localized-date-field">
                    <input
                      ref={sessionStartTimeInputRef}
                      type="text"
                      placeholder="HH:MM"
                      required
                    />
                    <button type="button" className="localized-date-trigger" aria-label="Abrir selector de hora inicial" onClick={openStartTimePicker}>
                      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                        <path d="M12 1.75A10.25 10.25 0 1 0 22.25 12 10.262 10.262 0 0 0 12 1.75zm0 18.5A8.25 8.25 0 1 1 20.25 12 8.259 8.259 0 0 1 12 20.25zm.75-13h-1.5v5.25l4.5 2.7.75-1.23-3.75-2.22z" />
                      </svg>
                    </button>
                  </div>
                </label>

                <label>
                  Hora fin
                  <div className="localized-date-field">
                    <input
                      ref={sessionEndTimeInputRef}
                      type="text"
                      placeholder="HH:MM"
                      required
                    />
                    <button type="button" className="localized-date-trigger" aria-label="Abrir selector de hora fin" onClick={openEndTimePicker}>
                      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                        <path d="M12 1.75A10.25 10.25 0 1 0 22.25 12 10.262 10.262 0 0 0 12 1.75zm0 18.5A8.25 8.25 0 1 1 20.25 12 8.259 8.259 0 0 1 12 20.25zm.75-13h-1.5v5.25l4.5 2.7.75-1.23-3.75-2.22z" />
                      </svg>
                    </button>
                  </div>
                </label>
              </div>

              <label className="span-2">
                Notas
                <textarea
                  value={form.notes}
                  onChange={(event) => setForm((prev) => ({ ...prev, notes: event.target.value }))}
                  placeholder="Notas previas a la sesión..."
                />
              </label>

              <div className="inline-actions span-2 session-modal-actions">
                <button type="button" className="button button-ghost" onClick={closeModal}>
                  Cancelar
                </button>
                <button className="button button-primary" type="submit">
                  {editingId ? 'Guardar cambios' : 'Programar sesión'}
                </button>
              </div>
            </form>
          </div>
        </div>
      ) : null}

      {sessionToDelete ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label="Eliminar sesión">
          <div className="modal-card">
            <div className="modal-head">
              <h3>Eliminar sesión</h3>
              <button type="button" className="modal-close" onClick={() => setSessionToDelete(null)} aria-label="Cerrar">
                X
              </button>
            </div>

            <p>
              Vas a eliminar la sesión de {sessionToDelete.patient_name || 'este niño'} del día {formatDate(sessionToDelete.session_date)}.
            </p>
            <p>Esta acción no se puede deshacer.</p>

            <div className="inline-actions session-modal-actions">
              <button type="button" className="button button-ghost" onClick={() => setSessionToDelete(null)}>
                Cancelar
              </button>
              <button type="button" className="button button-danger" onClick={removeSession}>
                Eliminar
              </button>
            </div>
          </div>
        </div>
      ) : null}

      {completionModalSession ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label={completionModalResult === 'problem' ? 'Registrar incidencia en la sesión' : 'Marcar sesión como completada'}>
          <div className="modal-card">
            <div className="modal-head">
              <h3>{completionModalResult === 'problem' ? 'Registrar incidencia en la sesión' : 'Marcar sesión como completada'}</h3>
              <button type="button" className="modal-close" onClick={closeCompletionModal} aria-label="Cerrar">
                X
              </button>
            </div>

            <p>
              {completionModalResult === 'problem'
                ? `Se registrará una incidencia en la sesión de ${completionModalSession.patient_name || 'este niño'}. Podrás consultarla despues en el historial de sesiones completadas.`
                : `Esta sesión de ${completionModalSession.patient_name || 'este niño'} se guardará como completada en el historial de sesiones.`}
            </p>

            {!completionModalNotes.trim() ? (
              <p className="form-hint">Esta sesión no tiene notas registradas. ¿Deseas añadirlas antes de continuar?</p>
            ) : (
              <p className="form-hint">Puedes revisar o editar las notas antes de continuar.</p>
            )}

            <label>
              Notas
              <textarea
                value={completionModalNotes}
                onChange={(event) => setCompletionModalNotes(event.target.value)}
                placeholder="Notas de la sesión..."
                rows={5}
              />
            </label>

            <div className="inline-actions session-modal-actions">
              <button type="button" className="button button-ghost" onClick={closeCompletionModal}>
                Cancelar
              </button>
              <button
                type="button"
                className={completionModalResult === 'problem' ? 'button button-danger' : 'button button-success'}
                onClick={confirmCompletionModal}
              >
                {completionModalResult === 'problem' ? 'Registrar incidencia' : 'Marcar completada'}
              </button>
            </div>
          </div>
        </div>
      ) : null}
    </section>
  );
}

function roleLabel(role) {
  switch (role) {
    case 'root':
      return 'Root';
    case 'admin':
      return 'Administrador';
    case 'terapeuta':
      return 'Terapeuta';
    case 'usuario':
      return 'Familia';
    default:
      return role || '';
  }
}

function NotificationsPage() {
  const { notify, session } = useApp();
  const [conversations, setConversations] = useState([]);
  const [selectedId, setSelectedId] = useState(0);
  const [detail, setDetail] = useState(null);
  const [isComposeOpen, setIsComposeOpen] = useState(false);
  const [recipients, setRecipients] = useState([]);
  const [composeRecipientId, setComposeRecipientId] = useState('');
  const [composeBody, setComposeBody] = useState('');
  const [replyBody, setReplyBody] = useState('');
  const [isSending, setIsSending] = useState(false);
  const [conversationToDelete, setConversationToDelete] = useState(null);
  const messagesEndRef = useRef(null);

  const canCompose = session.user.role !== 'usuario';
  const canModerate = session.user.role !== 'usuario';

  const loadConversations = useCallback(async () => {
    try {
      const response = await api('/notifications');
      setConversations(response.items || []);
    } catch (error) {
      notify('error', error.message || 'No se pudieron cargar las notificaciones.');
    }
  }, [notify]);

  useEffect(() => {
    loadConversations();
  }, [loadConversations]);

  const openConversation = useCallback(async (id) => {
    setSelectedId(id);
    try {
      const response = await api(`/notifications/${id}`);
      setDetail(response);
      setConversations((prev) => prev.map((item) => (item.id === id ? { ...item, unread_count: 0 } : item)));
    } catch (error) {
      notify('error', error.message || 'No se pudo abrir la conversacion.');
    }
  }, [notify]);

  useEffect(() => {
    if (messagesEndRef.current) {
      messagesEndRef.current.scrollIntoView({ block: 'end' });
    }
  }, [detail]);

  const openCompose = async () => {
    try {
      const response = await api('/notifications/recipients');
      setRecipients(response.recipients || []);
      setComposeRecipientId('');
      setComposeBody('');
      setIsComposeOpen(true);
    } catch (error) {
      notify('error', error.message || 'No se pudieron cargar los destinatarios.');
    }
  };

  const closeCompose = () => setIsComposeOpen(false);

  const submitCompose = async (event) => {
    event.preventDefault();
    if (!composeRecipientId || !composeBody.trim()) {
      notify('error', 'Selecciona un destinatario y escribe un mensaje.');
      return;
    }

    setIsSending(true);
    try {
      const response = await api('/notifications', {
        method: 'POST',
        body: { recipient_id: Number(composeRecipientId), body: composeBody.trim() },
      });
      notify('success', 'Mensaje enviado.');
      setIsComposeOpen(false);
      await loadConversations();
      if (response.conversationId) {
        openConversation(response.conversationId);
      }
    } catch (error) {
      notify('error', error.message || 'No se pudo enviar el mensaje.');
    } finally {
      setIsSending(false);
    }
  };

  const submitReply = async (event) => {
    event.preventDefault();
    if (!replyBody.trim() || !selectedId) {
      return;
    }

    setIsSending(true);
    try {
      const response = await api(`/notifications/${selectedId}/messages`, {
        method: 'POST',
        body: { body: replyBody.trim() },
      });
      setDetail((prev) => (prev ? { ...prev, messages: response.messages } : prev));
      setReplyBody('');
      loadConversations();
    } catch (error) {
      notify('error', error.message || 'No se pudo enviar el mensaje.');
    } finally {
      setIsSending(false);
    }
  };

  const toggleLock = async () => {
    if (!detail || !selectedId) {
      return;
    }

    const action = detail.conversation.is_locked ? 'unlock' : 'lock';
    try {
      await api(`/notifications/${selectedId}/${action}`, { method: 'POST' });
      notify('success', action === 'lock' ? 'Chat bloqueado.' : 'Chat desbloqueado.');
      const response = await api(`/notifications/${selectedId}`);
      setDetail(response);
      loadConversations();
    } catch (error) {
      notify('error', error.message || 'No se pudo actualizar el chat.');
    }
  };

  const isChatLocked = Boolean(detail && detail.conversation.is_locked);
  const canSendReply = Boolean(detail) && !(isChatLocked && session.user.role === 'usuario');
  const otherParticipant = detail ? detail.participants.find((participant) => participant.id !== session.user.id) : null;
  const canLockConversation = canModerate && Boolean(otherParticipant) && !['root', 'admin'].includes(otherParticipant.role);

  const removeConversation = async () => {
    if (!conversationToDelete) {
      return;
    }

    try {
      await api(`/notifications/${conversationToDelete.id}`, { method: 'DELETE' });
      notify('success', 'Chat eliminado.');
      setConversationToDelete(null);
      if (selectedId === conversationToDelete.id) {
        setSelectedId(0);
        setDetail(null);
      }
      loadConversations();
    } catch (error) {
      notify('error', error.message || 'No se pudo eliminar el chat.');
    }
  };

  return (
    <section className="notifications-page">
      <div className="notifications-list-panel list-card">
        <div className="notifications-list-head">
          <h2>Notificaciones</h2>
          {canCompose ? (
            <button type="button" className="button button-primary" onClick={openCompose}>
              Nuevo mensaje
            </button>
          ) : null}
        </div>
        <div className="notifications-list">
          {conversations.length === 0 ? (
            <p className="notifications-empty">No tienes conversaciones todavia.</p>
          ) : (
            conversations.map((item) => (
              <div className="notification-item-row" key={item.id}>
                <button
                  type="button"
                  className={`notification-item ${selectedId === item.id ? 'is-active' : ''} ${Number(item.unread_count) > 0 ? 'is-unread' : ''}`}
                  onClick={() => openConversation(item.id)}
                >
                  <span className="notification-item-head">
                    <strong>{item.other_user_name || 'Usuario eliminado'}</strong>
                    {item.is_locked ? (
                      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" className="notification-lock-icon">
                        <path d="M12 1a5 5 0 0 0-5 5v3H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2h-1V6a5 5 0 0 0-5-5zm-3 8V6a3 3 0 1 1 6 0v3zm3 4a2 2 0 0 1 1 3.73V19h-2v-2.27A2 2 0 0 1 12 13z" />
                      </svg>
                    ) : null}
                  </span>
                  <span className="notification-item-role">{roleLabel(item.other_user_role)}</span>
                  <span className="notification-item-preview">
                    {item.last_message_sender_id === session.user.id ? 'Tu: ' : ''}
                    {item.last_message_body || 'Sin mensajes'}
                  </span>
                  <span className="notification-item-meta">
                    <span>{formatDateTime(item.last_message_at || item.created_at)}</span>
                    {Number(item.unread_count) > 0 ? (
                      <span className="notification-unread-badge">{item.unread_count}</span>
                    ) : null}
                  </span>
                </button>
                {Number(item.unread_count) === 0 ? (
                  <IconActionButton
                    icon="delete"
                    label="Eliminar chat"
                    tone="danger"
                    onClick={(event) => {
                      event.stopPropagation();
                      setConversationToDelete(item);
                    }}
                  />
                ) : null}
              </div>
            ))
          )}
        </div>
      </div>

      <div className="notifications-thread-panel list-card">
        {!detail ? (
          <div className="notifications-thread-empty">Selecciona una conversacion para ver los mensajes.</div>
        ) : (
          <>
            <div className="notifications-thread-head">
              <div>
                <strong>{otherParticipant ? otherParticipant.name : 'Usuario'}</strong>
                <small>{roleLabel(otherParticipant ? otherParticipant.role : '')}</small>
              </div>
              {canLockConversation ? (
                <button type="button" className="button button-ghost" onClick={toggleLock}>
                  {isChatLocked ? 'Desbloquear chat' : 'Bloquear chat'}
                </button>
              ) : null}
            </div>

            <div className="notifications-thread-messages">
              {detail.messages.map((message) => (
                <div
                  key={message.id}
                  className={`chat-bubble ${message.sender_id === session.user.id ? 'chat-bubble-mine' : 'chat-bubble-theirs'}`}
                >
                  <p>{message.body}</p>
                  <small>{formatDateTime(message.created_at)}</small>
                </div>
              ))}
              <div ref={messagesEndRef} />
            </div>

            {isChatLocked ? (
              <div className="notifications-locked-banner">
                Este chat ha sido bloqueado{session.user.role === 'usuario' ? ' por la terapeuta.' : '.'}
              </div>
            ) : null}

            {canSendReply ? (
              <form className="notifications-reply-form" onSubmit={submitReply}>
                <textarea
                  value={replyBody}
                  onChange={(e) => setReplyBody(e.target.value)}
                  placeholder="Escribe un mensaje..."
                  rows={2}
                />
                <button type="submit" className="button button-primary" disabled={isSending || !replyBody.trim()}>
                  Enviar
                </button>
              </form>
            ) : null}
          </>
        )}
      </div>

      {isComposeOpen ? (
        <div className="modal-backdrop" role="dialog" aria-modal="true" aria-label="Nuevo mensaje">
          <div className="modal-card">
            <div className="modal-head">
              <h3>Nuevo mensaje</h3>
              <button type="button" className="modal-close" onClick={closeCompose} aria-label="Cerrar">
                X
              </button>
            </div>
            <form className="stack" onSubmit={submitCompose}>
              <label>
                Destinatario
                <select value={composeRecipientId} onChange={(e) => setComposeRecipientId(e.target.value)} required>
                  <option value="">Seleccionar</option>
                  {recipients.map((recipient) => (
                    <option key={recipient.id} value={recipient.id}>
                      {recipient.name} ({roleLabel(recipient.role)}
                      {recipient.linked_patient_name ? ` - ${recipient.linked_patient_name}` : ''})
                    </option>
                  ))}
                </select>
              </label>
              <label>
                Mensaje
                <textarea value={composeBody} onChange={(e) => setComposeBody(e.target.value)} rows={4} required />
              </label>
              <div className="inline-actions">
                <button className="button button-primary" type="submit" disabled={isSending}>
                  Enviar
                </button>
                <button type="button" className="button button-ghost" onClick={closeCompose}>
                  Cancelar
                </button>
              </div>
            </form>
          </div>
        </div>
      ) : null}

      <ConfirmActionModal
        open={Boolean(conversationToDelete)}
        title="Eliminar chat"
        message={conversationToDelete ? `Vas a eliminar el chat con "${conversationToDelete.other_user_name || 'Usuario eliminado'}". Esta accion no se puede deshacer.` : ''}
        onCancel={() => setConversationToDelete(null)}
        onConfirm={removeConversation}
        confirmLabel="Eliminar"
      />
    </section>
  );
}

function SimpleModulePage({ title, apiPath }) {
  const { notify } = useApp();
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    api(apiPath)
      .then(() => setLoaded(true))
      .catch((error) => notify('error', error.message || `No se pudo cargar ${title}.`));
  }, [apiPath, title, notify]);

  return (
    <section className="list-card">
      <h2>{title}</h2>
      <p>{loaded ? `Modulo ${title} preparado para evolucionar en React.` : 'Cargando...'}</p>
    </section>
  );
}

function SimpleTable({ columns, rows }) {
  const pageSizeOptions = [10, 20, 50];
  const [pageSize, setPageSize] = useState(10);
  const [currentPage, setCurrentPage] = useState(1);

  const totalRows = rows.length;
  const totalPages = Math.max(1, Math.ceil(totalRows / pageSize));

  useEffect(() => {
    setCurrentPage(1);
  }, [pageSize]);

  useEffect(() => {
    if (currentPage > totalPages) {
      setCurrentPage(totalPages);
    }
  }, [currentPage, totalPages]);

  const pagedRows = useMemo(() => {
    const start = (currentPage - 1) * pageSize;
    return rows.slice(start, start + pageSize);
  }, [rows, currentPage, pageSize]);

  const from = totalRows === 0 ? 0 : (currentPage - 1) * pageSize + 1;
  const to = Math.min(totalRows, currentPage * pageSize);

  return (
    <>
      {rows.length > 0 ? (
        <div className="table-top-controls">
          <label>
            Filas por pagina
            <select
              value={pageSize}
              onChange={(event) => setPageSize(Number(event.target.value || 10))}
              className="table-page-size"
            >
              {pageSizeOptions.map((size) => (
                <option key={size} value={size}>{size}</option>
              ))}
            </select>
          </label>
        </div>
      ) : null}

      <div className="table-wrap">
        <table>
          <thead>
            <tr>
              {columns.map((col) => (
                <th key={col}>{col}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {rows.length === 0 ? (
              <tr>
                <td colSpan={columns.length}>Sin datos.</td>
              </tr>
            ) : (
              pagedRows.map((cells, index) => (
                <tr key={`${currentPage}-${index}`}>
                  {cells.map((cell, i) => (
                    <td key={i}>{cell}</td>
                  ))}
                </tr>
              ))
            )}
          </tbody>
        </table>
      </div>

      {rows.length > 0 ? (
        <div className="table-pagination">
          <div className="table-pagination-meta">
            <span>Mostrando {from}-{to} de {totalRows}</span>
          </div>

          <div className="table-pagination-controls">
            <button
              type="button"
              className="button button-ghost pagination-button pagination-arrow"
              onClick={() => setCurrentPage((prev) => Math.max(1, prev - 1))}
              disabled={currentPage === 1}
              aria-label="Pagina anterior"
              title="Pagina anterior"
            >
              <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                <path d="M15.4 5.4 8.8 12l6.6 6.6-1.4 1.4L6 12l8-8z" />
              </svg>
            </button>
            <span>Pagina {currentPage} de {totalPages}</span>
            <button
              type="button"
              className="button button-ghost pagination-button pagination-arrow"
              onClick={() => setCurrentPage((prev) => Math.min(totalPages, prev + 1))}
              disabled={currentPage === totalPages}
              aria-label="Pagina siguiente"
              title="Pagina siguiente"
            >
              <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                <path d="m8.6 5.4 1.4-1.4 8 8-8 8-1.4-1.4 6.6-6.6z" />
              </svg>
            </button>
          </div>
        </div>
      ) : null}
    </>
  );
}

function emptyPatient() {
  return {
    first_name: '',
    last_name: '',
    birth_date: '',
    diagnosis: '',
    allergies: '',
    school: '',
    guardian_name: '',
    guardian_phone: '',
    assigned_therapist_name: '',
    guardian_email: '',
    address: '',
    observations: '',
  };
}

function emptyDocument() {
  return {
    patient_id: '',
    therapist_id: '',
    therapy_id: '',
    title: '',
    document_type: 'Evaluacion inicial',
    session_date: '',
    summary: '',
    content: '',
    objectives: '',
    achievements: '',
    recommendations: '',
    visible_to_parents: false,
    attachments: [],
  };
}

function emptyBillingEntry() {
  return {
    patient_id: '',
    concept: '',
    issue_date: '',
    due_date: '',
    amount: '',
    status: 'pending',
    tax_name: 'IPSI',
    tax_rate: '0.00',
    payment_method: '',
    notes: '',
    paid_at: '',
  };
}

function emptyAccountingEntry() {
  return {
    entry_date: '',
    entry_type: 'expense',
    category: ACCOUNTING_CATEGORY_OPTIONS.expense[0],
    concept: '',
    amount: '',
    payment_method: '',
    notes: '',
    attachment_name: '',
    attachment_type: '',
    attachment_data_url: '',
    related_billing_entry_id: '',
  };
}

function Root() {
  return (
    <RouterProvider>
      <AppProvider>
        <AppShell />
      </AppProvider>
    </RouterProvider>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Root />);
