ixiclinicDocs
Desarrolladores

Convenciones

Reglas de código, patrones y particularidades del dominio que hay que respetar en ixiclinic.

Estas son las convenciones no negociables del monorepo. Respétalas: varias parecen arbitrarias pero responden a decisiones de arquitectura o a la realidad del dominio (un laboratorio clínico en República Dominicana).

Path aliases

Todas las apps usan @/*./src/*. Importa siempre con el alias en lugar de rutas relativas largas:

import { cn } from "@/lib/utils";
import { patientSchema } from "@/lib/validators/patient";

Sin estado de cliente en el admin

En apps/admin no se hace fetch desde el navegador ni se mantiene estado de cliente para los datos. Toda la obtención y mutación de datos pasa por server actions en apps/admin/src/actions/, que a su vez usan el cliente del API (apps/admin/src/lib/api-client.ts): apiGet, apiPost, apiPatch, apiPut, apiDelete.

Un server action típico:

"use server";

import { apiGet, apiPost } from "@/lib/api-client";
import { revalidatePath } from "next/cache";

export async function getPatients(search?: string) {
  const params = search ? `?search=${encodeURIComponent(search)}` : "";
  const result = await apiGet<{ data: any[]; total: number }>(`/api/patients${params}`);
  return result.data;
}

export async function createPatientData(data: Record<string, unknown>) {
  try {
    const result = await apiPost<any>("/api/patients", data);
    revalidatePath("/patients");
    return result;
  } catch (e: unknown) {
    // Devuelve el error como objeto estructurado en vez de lanzarlo: una
    // excepción lanzada desde un server action aparece en prod como un fallo
    // genérico de "Server Components render" sin el mensaje real.
    return { error: e instanceof Error ? e.message : "Error al crear el paciente" };
  }
}

Nota: Un error lanzado dentro de un server action se muestra en producción como un fallo genérico de render de Server Components, sin el mensaje real. Por eso los actions de mutación capturan el error y devuelven { error: string } en lugar de propagarlo.

El mismo patrón de server actions + api-client.ts se usa en apps/console y apps/portal, cada uno con sus propias cookies y endpoints.

Validación con Zod 4

El proyecto usa Zod v4. Hay una diferencia de API que rompe respecto a v3: para records usa la firma de dos argumentos.

// Correcto en Zod 4:
z.record(z.string(), z.unknown())
// Incorrecto (firma de Zod 3):
z.record(z.unknown())

Los validators del admin viven en apps/admin/src/lib/validators/<dominio>.ts y siempre extraen el tipo con z.infer:

import { z } from "zod";

export const patientSchema = z.object({
  firstName: z.string().min(1, "Nombre requerido"),
  lastName: z.string().min(1, "Apellido requerido"),
  sexAtBirth: z.enum(["M", "F"]).optional(),
  email: z
    .string()
    .optional()
    .transform((val) => (val === "" ? undefined : val))
    .pipe(z.string().email("Email invalido").optional()),
});

export type PatientInput = z.infer<typeof patientSchema>;

Los mensajes de validación están en español porque se muestran directo al usuario. Los formularios usan react-hook-form con @hookform/resolvers/zod.

Next.js 16

Las apps Next.js usan Next.js 16, que tiene cambios que rompen respecto a versiones anteriores. Consulta la documentación oficial de Next.js 16 antes de escribir código de Next (rutas, layouts, server actions, caché) en lugar de asumir patrones de versiones previas.

Zustand solo en lab-site

El único lugar con estado de cliente con Zustand es apps/lab-site (el sitio público por tenant): el carrito está persistido con un namespace específico por tenant (apps/lab-site/src/store/). No introduzcas stores de Zustand en el admin/console/portal — ahí el patrón es server actions.

Dominio: República Dominicana

El dominio es un laboratorio clínico dominicano, así que es esperable y correcto encontrar:

  • DGII / e‑CF — cumplimiento fiscal electrónico (NCF, comprobantes).
  • ARS — seguros de salud, coberturas, autorizaciones y reclamaciones.
  • Términos clínicos y de dominio en español (cédula, RNC, sucursal, muestra, etc.).

No "internacionalices" ni traduzcas estos términos: son parte del modelo de negocio.

Naming mixto ES/EN en las rutas

Las rutas del admin mezclan inglés y español de forma deliberada. Conviven, por ejemplo, carpetas en español (actividades, encuentros, recetas) junto a carpetas en inglés (orders, billing, patients, results) dentro de apps/admin/src/app/(dashboard)/.

Esto es la realidad del repo, no un error a corregir: las rutas en español corresponden a módulos clínicos/funcionales agregados más recientemente (módulo de programas crónicos, recetas, etc.). Al crear features nuevas, sigue el nombre que ya use el módulo al que pertenece tu cambio en lugar de imponer un idioma.

Helpers de utilidad

Los helpers compartidos del admin están en apps/admin/src/lib/utils.ts:

HelperQué hace
cn(...inputs)Combina clases de Tailwind con clsx + tailwind-merge
formatCedula(cedula)Formatea una cédula dominicana de 11 dígitos como 000-0000000-0
formatPhone(phone)Formatea un teléfono de 10 dígitos como (000) 000-0000
reloadAppTo(path)Navega la ventana top (escapa del iframe del Lab OS) en cambios de identidad
import { cn, formatCedula, formatPhone } from "@/lib/utils";

Nota: formatCedula y formatPhone devuelven la cadena original sin cambios si no tiene la longitud esperada (11 y 10 dígitos respectivamente), así que es seguro pasarles valores parciales o vacíos.

Para recetas concretas (crear un módulo del API, añadir un server action, etc.) ver las Guías.

On this page