Documentation Index
Fetch the complete documentation index at: https://blog.mapptech.com.co/llms.txt
Use this file to discover all available pages before exploring further.
Pasos del Flujo
- Interacción del Cliente (Capa de Presentación): La interacción del usuario ocurre en un componente React (
page.tsx o container/ opcional) dentro de una aplicación apps/*.
- Llamada a Server Action (Punto de Entrada API): El componente invoca una función Server Action importada (
@core/actions). Esta función está marcada con 'use server'.
- Validación de Entrada (Action): La Server Action valida la entrada cruda recibida del cliente usando esquemas Yup compartidos (
@core/validation). Si la validación falla, devuelve un error inmediatamente (típicamente un ActionResult con success: false y validationErrors).
- Instanciar e Iniciar Máquina (Action): Si la validación tiene éxito, la Action:
- Resuelve cualquier dependencia necesaria, como la implementación correcta del Service usando su factory (
@core/services/[domain]/serviceMap.ts).
- Instancia (
interpret) la máquina XState relevante (@core/machines), proporcionando la instancia del servicio resuelta y potencialmente otras configuraciones a través del context inicial.
- Inicia el intérprete de la máquina.
- Envía el evento inicial (ej.,
{ type: 'CREATE', input: validatedInput }) a la máquina, pasando la entrada validada (ej., desde InferType de Yup).
- Escucha/espera a que la máquina alcance un estado final.
- Orquestar Lógica (Machine): La máquina XState transiciona a través de sus estados basada en eventos y su contexto interno. Su rol principal es la orquestación.
- Preparar Entrada de Servicio e Invocar (Machine): Dentro de estados específicos (a menudo usando servicios
invoke o acciones entry/exit), la máquina:
- Construye el DTO de Entrada del Servicio apropiado (definido en
services/[domain]/domain/[Domain]Types.ts) usando datos de su contexto (como el validatedInput recibido en el evento inicial).
- Llama al método apropiado en la implementación del Servicio (pasado vía contexto) usando el DTO preparado (ej.,
context.userService.createUser(serviceInputDto)).
- Procesar Salida de Servicio y Actualizar Contexto (Machine): Cuando la llamada al servicio invocada se completa:
- La máquina recibe el DTO de Salida del Servicio (definido en
services/[domain]/domain/[Domain]Types.ts).
- Basándose en la estructura de este DTO de salida, la máquina actualiza su contexto interno (ej., extrayendo la entidad
User creada desde outputDto.user y almacenándola en context.user, o almacenando un mensaje de error de un DTO de salida fallido en context.error).
- Alcanzar Estado Final (Machine): La máquina eventualmente transiciona a un estado terminal (ej.,
success, failure), indicado por type: 'final'.
- Devolver Resultado Estructurado (Action → Client): La Server Action, habiendo esperado el estado final de la máquina:
- Inspecciona el estado final y el contexto de la máquina.
- Formatea el resultado en un objeto
ActionResult estándar (ej., { success: true, data: finalMachineContext.user } o { success: false, error: finalMachineContext.error }).
- Devuelve este
ActionResult al componente cliente que realizó la llamada.
Roles Claros:
- Client (
apps/*): Renderiza UI (mappnext/ds-tw), captura entrada del usuario, llama a Actions, muestra resultados/errores desde ActionResult.
- Action (
@core/actions): Límite de la API. Valida entrada (Yup), resuelve dependencias (Services), interpreta e inicia la Machine, espera resultado, formatea ActionResult.
- Machine (
@core/machines): Orquesta el flujo (XState). Mapea entrada validada a DTOs de Servicio, invoca métodos del Servicio, procesa DTOs de salida del Servicio, gestiona estado/contexto interno.
- Service (
@core/services): Ejecuta tareas discretas. Define contrato (Interface + DTOs en domain/), implementa lógica (Classes en implementations/), interactúa con DB/APIs externas.
Ejemplo Abreviado: Flujo Crear Usuario (Action → Machine → Service)
Este ejemplo simplificado demuestra el flujo central: Action valida e inicia la máquina, la máquina prepara un DTO y llama al servicio, el servicio realiza el trabajo usando su contrato definido, y el resultado fluye de regreso.
1. Tipos Base (@core/types)
// packages/core/types/entities/User.ts
// Define la entidad User
export interface User {
id: string;
name: string;
email: string;
createdAt: Date;
}
// packages/core/types/machines/user/UserCreationMachineTypes.ts
import { User } from '../../entities/User';
import { CreateUserInput } from '../../../validation/userSchema'; // Tipo de entrada desde la capa de validación
import { UserService } from '../../../services/user/domain/UserService'; // Interfaz del Servicio
// Contexto de la Máquina: Almacena entrada validada, resultado final (User), error e instancia del servicio.
export interface UserCreationMachineContext {
validatedInput?: CreateUserInput; // Contiene datos *después* de la validación Yup
user?: User; // Contiene la entidad User final *después* de la llamada exitosa al servicio y procesamiento
error?: string;
userService?: UserService; // La instancia del servicio resuelta
}
// Eventos de la Máquina
export type UserCreationMachineEvent =
| { type: 'CREATE'; input: CreateUserInput } // El evento lleva la entrada validada
| { type: 'RETRY' };
2. Esquema de Validación (@core/validation)
// packages/core/validation/userSchema.ts
import * as yup from 'yup';
export const userSchema = yup.object({
name: yup.string().required("Name is required"), // El nombre es requerido
email: yup.string().email("Invalid email format").required("Email is required"), // Formato de email inválido, El email es requerido
});
// Tipo derivado del esquema de validación
export type CreateUserInput = yup.InferType<typeof userSchema>;
3. Definición del Servicio (@core/services/user/domain)
// packages/core/services/user/domain/UserTypes.ts
import { User } from '../../../types/entities/User';
// --- DTOs de Métodos de Servicio ---
// Define las formas de datos específicas para los métodos de UserService
// DTO de entrada para el método createUser
export interface CreateUserMethodInput {
name: string;
email: string;
}
// DTO de salida para el método createUser
// Caso simple: devuelve la entidad User creada.
export interface CreateUserMethodOutput {
user: User;
}
// packages/core/services/user/domain/UserService.ts
// --- Interfaz del Servicio (Contrato) ---
// Define *qué* operaciones proporciona el servicio, usando los DTOs para las formas de datos.
import { CreateUserMethodInput, CreateUserMethodOutput } from './UserTypes';
export interface UserService {
createUser(data: CreateUserMethodInput): Promise<CreateUserMethodOutput>;
// Define otros métodos de servicio aquí usando sus respectivos DTOs desde UserTypes.ts
}
4. Implementación del Servicio (@core/services/user/implementations)
// packages/core/services/user/implementations/DefaultUserService.ts
import { UserService } from '../domain/UserService';
// *** Importa los DTOs de Método específicos definidos por el contrato del servicio ***
import { CreateUserMethodInput, CreateUserMethodOutput } from '../domain/UserTypes';
import { User } from '../../../types/entities/User'; // Necesario para construir la entidad interna/salida
// Implementación Concreta de la Strategy usando el contrato DTO definido
export class DefaultUserService implements UserService {
// *** La firma del método usa los DTOs del servicio ***
async createUser(data: CreateUserMethodInput): Promise<CreateUserMethodOutput> {
console.log('DefaultUserService: Received service input DTO:', data); // DTO de entrada del servicio recibido
// Simula interacción con BD o lógica de negocio usando datos del DTO de entrada
const newUserEntity: User = { // Construye la entidad interna
id: crypto.randomUUID(),
name: data.name, // Usa datos del DTO de entrada
email: data.email, // Usa datos del DTO de entrada
createdAt: new Date(),
};
// await db.users.insert(newUserEntity); // Llamada real a la base de datos
console.log('DefaultUserService: User entity created:', newUserEntity); // Entidad User creada
// *** Devuelve el DTO de Salida del Servicio definido ***
// (Coincidiendo con la estructura en UserTypes.ts)
return { user: newUserEntity };
}
}
5. Resolución del Servicio (@core/services/user/serviceMap.ts & index.ts)
// packages/core/services/user/serviceMap.ts
import { UserService } from './domain/UserService';
import { DefaultUserService } from './implementations/DefaultUserService';
const serviceImplementations: Record<string, UserService> = {
default: new DefaultUserService(),
};
// Función Factory para obtener la implementación de servicio apropiada
export function getUserService(type: string = 'default'): UserService {
const serviceInstance = serviceImplementations[type.toLowerCase()];
if (!serviceInstance) {
console.warn(`UserService strategy type "${type}" not found, falling back to default.`); // Tipo de estrategia no encontrado, volviendo al default.
return serviceImplementations['default']; // Asegura que el default exista
}
return serviceInstance;
}
// packages/core/services/user/index.ts
// Archivo Barrel exportando la función factory
export { getUserService } from './serviceMap';
6. Máquina XState (@core/machines)
// packages/core/machines/userCreationMachine.ts
import { createMachine, assign } from 'xstate';
import {
UserCreationMachineContext,
UserCreationMachineEvent
} from '../types/machines/user/UserCreationMachineTypes';
// *** Importa los DTOs de Servicio necesarios para la invocación y procesamiento del resultado ***
import { CreateUserMethodInput, CreateUserMethodOutput } from '../services/user/domain/UserTypes';
import { UserService } from '../services/user/domain/UserService'; // Para tipar el contexto
export const userCreationMachine = createMachine({
id: 'userCreation',
types: {} as { context: UserCreationMachineContext, events: UserCreationMachineEvent },
initial: 'idle',
// El Context contiene la entrada validada, resultado potencial (user), error y servicio
context: {
validatedInput: undefined,
user: undefined,
error: undefined,
userService: undefined,
},
states: {
idle: {
on: {
CREATE: {
target: 'creating',
// Guard asegura que la instancia del servicio esté disponible en el contexto
guard: ({ context }) => !!context.userService,
// Action asigna la entrada validada del evento al contexto
actions: assign({
validatedInput: ({ event }) => event.input,
error: undefined, // Limpia errores previos
}),
},
},
},
creating: {
// Invoca la llamada al servicio
invoke: {
id: 'invokeCreateUserService',
// La función src prepara el DTO y llama al método del servicio
src: async ({ context }) => {
// Type guards por seguridad
if (!context.userService) throw new Error('UserService missing in context'); // Falta UserService en el contexto
if (!context.validatedInput) throw new Error('Validated input missing in context'); // Falta entrada validada en el contexto
// *** Prepara el DTO CreateUserMethodInput para la llamada al servicio ***
// Mapea datos desde context.validatedInput de la máquina a la forma del DTO de servicio.
const serviceInput: CreateUserMethodInput = {
name: context.validatedInput.name,
email: context.validatedInput.email,
};
console.log('Machine: Calling createUser service with DTO:', serviceInput); // Máquina: Llamando al servicio createUser con DTO:
// *** La Máquina invoca el método del servicio usando el DTO preparado ***
return await context.userService.createUser(serviceInput);
},
// onDone maneja el resultado exitoso (DTO de Salida) del servicio
onDone: {
target: 'success',
actions: assign({
// *** Procesa el DTO CreateUserMethodOutput ***
// Extrae los datos relevantes (la entidad User) del DTO de salida del servicio
// y asígnalos al campo context.user de la máquina.
user: ({ event }) => {
const serviceOutput = event.output as CreateUserMethodOutput;
console.log('Machine: Received service output DTO:', serviceOutput); // Máquina: DTO de salida del servicio recibido:
return serviceOutput.user; // Extrae la propiedad user basada en la estructura del DTO
},
}),
},
// onError maneja fallos durante la invocación del servicio
onError: {
target: 'failure',
actions: assign({
error: ({ event }) => (event.error as Error)?.message ?? 'User creation failed in service', // La creación de usuario falló en el servicio
}),
},
},
},
success: {
// Estado final de éxito
type: 'final',
},
failure: {
// Estado final de fallo (podría permitir reintentos)
on: { RETRY: 'creating' }
},
},
});
7. Server Action (@core/actions)
// packages/core/actions/userActions.ts
'use server';
import { userSchema, CreateUserInput } from '../validation/userSchema';
import { userCreationMachine } from '../machines/userCreationMachine';
import { getUserService } from '../services/user'; // Importa factory
import { User } from '../types/entities/User'; // Importa entidad para el tipo de resultado final
import { interpret } from 'xstate';
import * as yup from 'yup';
// Estructura de Resultado de Acción Estandarizada para el cliente
export interface ActionResult<T = any> {
success: boolean;
data?: T; // Contiene los datos finales relevantes (ej., el User creado)
error?: string;
validationErrors?: { path: string; message: string }[];
}
// La Server Action orquesta validación, resolución de servicio y ejecución de máquina
export async function createUserAction(
input: CreateUserInput // Action recibe entrada cruda (pero con suerte con la forma correcta) del cliente
): Promise<ActionResult<User>> { // Promete la entidad User final en caso de éxito
try {
// 1. Validar Entrada usando esquema Yup
await userSchema.validate(input, { abortEarly: false });
const validatedInput = input; // Usa la entrada post-validación
console.log('Action: Input validation successful'); // Validación de entrada exitosa
// 2. Resolver la Instancia de Servicio requerida usando la factory
const userService = getUserService(); // Obtener la implementación por defecto
console.log('Action: Resolved UserService instance'); // Instancia UserService resuelta
// 3. Interpretar y ejecutar la máquina XState
console.log('Action: Interpreting userCreationMachine'); // Interpretando userCreationMachine
// Usa una Promise para esperar a que la máquina alcance un estado final
return new Promise((resolve) => {
const machineActor = interpret(
// Proporciona contexto inicial a la máquina:
userCreationMachine.withContext({
userService: userService, // Inyecta el servicio resuelto
validatedInput: undefined, // La máquina empieza limpia, obtendrá entrada vía evento
user: undefined,
error: undefined,
})
).onTransition((state) => {
// 4. Resolver la Promise cuando la máquina alcanza un estado final
if (state.matches('success')) {
console.log('Action: Machine finished successfully. Final context:', state.context); // Máquina finalizada con éxito. Contexto final:
// Extrae los datos finales del User del contexto de la máquina
resolve({ success: true, data: state.context.user });
machineActor.stop(); // Detiene el intérprete
}
if (state.matches('failure')) {
console.error('Action: Machine finished with failure:', state.context.error); // Máquina finalizada con fallo:
resolve({ success: false, error: state.context.error });
machineActor.stop(); // Detiene el intérprete
}
})
.start(); // Inicia el intérprete de la máquina
// 5. Envía el evento inicial con la entrada validada para iniciar el proceso
console.log('Action: Sending CREATE event to machine with validated input:', validatedInput); // Enviando evento CREATE a la máquina con entrada validada:
machineActor.send({ type: 'CREATE', input: validatedInput });
});
} catch (error) {
// Manejar Errores de Validación específicamente
if (error instanceof yup.ValidationError) {
console.error('Action: Validation failed:', error.inner); // Validación fallida:
return {
success: false,
validationErrors: error.inner.map((err) => ({
path: err.path ?? 'unknown',
message: err.message,
})),
};
}
// Manejar Otros Errores Inesperados (ej., fallo en resolución de servicio, problemas de configuración de máquina)
console.error('Action: Unexpected error during setup:', error); // Error inesperado durante la configuración:
return {
success: false,
error: error instanceof Error ? error.message : 'An unexpected server setup error occurred.', // Ocurrió un error inesperado de configuración del servidor.
};
}
}
8. Página UI (apps/[appName]/app/.../page.tsx)
// apps/radicacion/app/crear-usuario/page.tsx (Ejemplo - UI Simplificada)
'use client';
import React, { useState, useTransition, FormEvent } from 'react';
import { createUserAction, ActionResult } from '@core/actions/userActions';
import { CreateUserInput } from '@core/validation/userSchema'; // La página usa el tipo de validación para el formulario
import { User } from '@core/types/entities/User'; // La página espera la entidad User final en el resultado
// --- Importa componentes reales del Design System ---
import { Button } from 'mappnext/ds-tw/atoms/Button'; // Reemplaza con tu ruta real
import { InputField } from 'mappnext/ds-tw/molecules/InputField'; // Reemplaza con tu ruta real
// --- ---
export default function CreateUserPage() {
// El estado del formulario coincide con la forma CreateUserInput (de la validación)
const [formData, setFormData] = useState<CreateUserInput>({ name: '', email: '' });
const [isPending, startTransition] = useTransition();
// El estado del resultado espera el ActionResult que contiene el User final o errores
const [result, setResult] = useState<ActionResult<User> | null>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
if (result) setResult(null); // Limpia resultado al cambiar el formulario
};
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
setResult(null); // Limpia resultado previo
startTransition(async () => {
// Llama a la server action con los datos del formulario
const actionResult = await createUserAction(formData);
setResult(actionResult); // Actualiza el estado con el resultado de la action
if (actionResult.success) {
setFormData({ name: '', email: '' }); // Resetea el formulario en caso de éxito
console.log("UI: User created!", actionResult.data); // ¡Usuario creado!
// Opcionalmente mostrar toast de éxito, redirigir, etc.
} else {
console.error("UI: Creation failed:", actionResult.error || actionResult.validationErrors); // Creación fallida:
// Errores de validación/servidor se muestran vía el estado result abajo
}
});
};
// Ayudante para obtener error de validación para una ruta de campo específica
const getValidationError = (path: string): string | undefined => {
return result?.validationErrors?.find(err => err.path === path)?.message;
};
return (
<div className="p-4 max-w-md mx-auto">
<h1 className="text-2xl font-bold mb-4">Crear Usuario (Ejemplo Simple)</h1>
<form onSubmit={handleSubmit} className="space-y-4">
{/* Campos de entrada vinculados al estado formData */}
<InputField
name="name"
label="Nombre" // Cambiado a Español
value={formData.name}
onChange={handleChange}
error={getValidationError('name')} // Muestra error de validación si existe
disabled={isPending}
required
/>
<InputField
name="email"
label="Email" // Se mantiene
type="email"
value={formData.email}
onChange={handleChange}
error={getValidationError('email')} // Muestra error de validación si existe
disabled={isPending}
required
/>
{/* Muestra error general del servidor (de fallo de máquina/servicio) */}
{result && !result.success && result.error && (
<p className="text-sm text-red-600 mt-2">Error: {result.error}</p>
)}
{/* Muestra mensaje de éxito */}
{result?.success && result.data && (
<p className="text-sm text-green-600 mt-2">¡Usuario "{result.data.name}" creado exitosamente!</p>
)}
{/* Botón de envío muestra estado pendiente */}
<Button type="submit" disabled={isPending} className="w-full mt-4">
{isPending ? 'Creando...' : 'Crear Usuario'}
</Button>
</form>
</div>
);
}
Resumen de Rutas de Importación de Ejemplo
Importaciones Típicas mostrando estructura correcta y uso de DTO
// Importaciones Core usando alias
import { createUserAction, ActionResult } from '@core/actions/userActions';
import { userSchema, CreateUserInput } from '@core/validation/userSchema';
import { getUserService } from '@core/services/user'; // Factory/Locator del Servicio (usado en configuración de Action/Machine)
import { DefaultUserService } from '@core/services/user/implementations/DefaultUserService'; // Implementación/Strategy específica (menos común fuera de tests/map)
import { UserService } from '@core/services/user/domain/UserService'; // Interfaz/Abstracción del Servicio (para tipado)
import { CreateUserMethodInput, CreateUserMethodOutput } from '@core/services/user/domain/UserTypes'; // DTOs de Métodos de Servicio (usados en Impl de Machine/Service)
import { userCreationMachine } from '@core/machines/userCreationMachine'; // Definición de Máquina (usada en Action)
import { User } from '@core/types/entities/User'; // Ejemplo de Tipo de Entidad Base
import {
UserCreationMachineContext,
UserCreationMachineEvent
} from '@core/types/machines/user/UserCreationMachineTypes'; // Ejemplo de Tipos de Máquina
import { PaginationResult } from '@core/types/common/PaginationResult'; // Ejemplo de Tipo Común
import { useDebounce } from '@hooks/useDebounce'; // Hook (Ejemplo)
import { getApiEndpoint } from '@config/endpoints'; // Config (Ejemplo)
// Importaciones del Design System (Reemplazar con rutas reales)
import { Button } from 'mappnext/ds-tw/atoms/Button';
import { InputField } from 'mappnext/ds-tw/molecules/InputField';
import { Modal } from 'mappnext/ds-tw/organisms/Modal';