# Documentación (/docs) ## Popular [#popular] - [Primeros pasos](/docs/getting-started): De una organización vacía a tu primera nómina cerrada, paso a paso y con capturas reales. - [Operar la nómina](/docs/payroll): Abre el periodo, captura incidencias, calcula, revisa los recibos y cierra. - [Cómo se calcula la nómina](/docs/calculation): De dónde sale cada importe del recibo: SBC, ISR y subsidio, exenciones, IMSS y deducciones. - [Portal de colaborador](/docs/employee-portal): Lo que ven tus colaboradores y sus supervisores, con acceso sin contraseñas. ## Guías por módulo [#guías-por-módulo] - [Dashboard](/docs/dashboard): El estado de la nómina y del personal de un vistazo, con accesos a cada módulo. - [Configuración](/docs/configuration): Razones sociales, registros patronales, estructura, calendarios, prestaciones, contratos y conceptos. - [Empleados](/docs/employees): Altas, bajas, reingresos, edición masiva, créditos y campos personalizados. - [Asistencia](/docs/attendance): Horarios, checadas, retardos y la captura de faltas y otras incidencias. - [Vacaciones](/docs/time-off): Saldos de vacaciones, la tabla de la ley y el ajuste de saldos iniciales. - [Finiquito y liquidación](/docs/terminations): Qué paga cada uno, cómo influye el motivo de la baja y cómo se calcula, se paga y se descarga su carta. - [CFDI de nómina](/docs/cfdi): Lo que ya puedes preparar para timbrar tus recibos. ## IA [#ia] - [Conectores de IA](/docs/ai/mcp): Conecta Claude, ChatGPT y otros asistentes para consultar y operar la nómina conversando. - [Asistente de IA — Próximamente](/docs/ai/assistant): Un asistente integrado en la plataforma que responde preguntas sobre tu nómina y cómo utilizar el sistema. # Desarrolladores (/docs/developers) ## Herramientas [#herramientas] - [Referencia de la API](/docs/developers/api-reference): Endpoints REST con ejemplos de solicitud y respuesta, autenticación con API keys y reglas de cada campo. - [Webhooks](/docs/developers/webhooks): Recibe un aviso firmado cuando se cierra un periodo. - [SDK — Próximamente](/docs/developers/sdk): Bibliotecas oficiales para integrarte desde tu lenguaje de programación preferido. ## Recursos de la API [#recursos-de-la-api] - [Empleados](/docs/developers/api-reference/employees/list): Aprende sobre el modelo de empleado y cómo listarlos, crearlos, editarlos y eliminarlos. - [Departamentos](/docs/developers/api-reference/departments/list): Aprende sobre el modelo de departamento, su jerarquía y cómo listarlos, crearlos y eliminarlos. - [Puestos](/docs/developers/api-reference/job-positions/list): Aprende sobre el modelo de puesto de trabajo y cómo consultarlo por número, nombre o código. ## Documentación para agentes [#documentación-para-agentes] **Markdown de cada página.** Cualquier página se lee como Markdown agregando `.md` a su dirección (por ejemplo, `/docs/developers.md`) o con el botón **Copiar página**. También responde en Markdown cuando la solicitud envía el encabezado `Accept: text/markdown`. **Índice para agentes.** [`/llms.txt`](/llms.txt) lista todas las páginas con su descripción y el enlace a su versión en Markdown. [`/llms-full.txt`](/llms-full.txt) reúne toda la documentación en un solo archivo. # Webhooks (/docs/developers/webhooks) Un webhook le avisa a tu sistema cuando ocurre un evento en la organización. Se registra en la pestaña **Webhooks** de una integración, como explica la guía de [Integraciones](/docs/configuration/access#integraciones). ## Eventos [#eventos] Una integración solo recibe los eventos que puede consultar con sus permisos. Si el permiso está limitado a algunas razones sociales, recibe solo los eventos de esas razones sociales. | Evento | Cuándo se envía | Permisos que necesita | | --------------------------- | ---------------------------------------- | --------------------------- | | `employee.created` | Se crea un empleado | Ver empleados y ver sueldos | | `employee.updated` | Se actualiza un empleado | Ver empleados y ver sueldos | | `employee.terminated` | Se da de baja un empleado | Ver empleados y ver sueldos | | `payroll_period.opened` | Se abre una corrida | Ver nómina | | `payroll_period.calculated` | Se calcula una corrida | Ver nómina | | `payroll_period.closed` | Se cierra una corrida | Ver nómina | | `receipt.stamped` | Un recibo queda timbrado | Ver nómina | | `receipt.failed` | Un recibo queda con error de timbrado | Ver nómina | | `time_off.approved` | Se aprueba una solicitud de tiempo libre | Ver incidencias | Un evento de empleado llega si todos sus permisos alcanzan una misma razón social del empleado; los demás, solo si alcanza todas las de la corrida, el recibo o la solicitud. El aviso de un periodo cerrado sale segundos después del cierre; el de un finiquito y los demás eventos, en menos de un minuto. ## Qué recibes [#que-recibes] Cada aviso es un `POST` con un cuerpo JSON. Solo lleva identificadores: consulta el resto en la API con tus permisos. ```json { "id": "f1bebfe5-9c1e-4eff-b228-8bc27b925b06", "type": "payroll_period.closed", "createdAt": "2026-11-03T19:26:33.002Z", "data": { "payrollRunId": "c1db0f37-a1c6-4ed8-b4fc-b85663f1c2ed", "payrollRunNumber": 1, "payrollType": "ordinary", "payrollCalendarNumber": 1, "periodNumber": "2026-21" } } ``` `data` cambia según el evento: | Eventos | Campos de `data` | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `employee.*` | `employeeId`, `employeeNumber` | | `payroll_period.*` | `payrollRunId`, `payrollRunNumber`, `payrollType`, `payrollCalendarNumber`, `periodNumber`. Los dos últimos son `null` si la corrida no tiene calendario o periodo. | | `receipt.*` | `receiptId`, `uuid`, `payrollRunNumber`, `employeeNumber`. `uuid` es `null` si el recibo no se timbró. | | `time_off.approved` | `timeOffRequestId`, `timeOffType`, `employeeNumber`, `startDate`, `endDate` | Y estas cabeceras: | Cabecera | Qué contiene | | --------------------- | ------------------------------------------------------------------------------------------------------- | | `X-Webhook-Id` | El id del evento. Es el mismo en cada reintento, así que utilízalo para no procesar dos veces un aviso. | | `X-Webhook-Event` | El tipo de evento | | `X-Webhook-Timestamp` | Segundos Unix del momento en que se envió este intento | | `X-Webhook-Signature` | `sha256=` seguido de la firma en hexadecimal | ## Verifica la firma [#verifica-la-firma] La firma es un HMAC-SHA256, con el **Secreto de firma** del webhook, de `.`. Calcúlala sobre el cuerpo tal como llegó, antes de interpretar el JSON, y rechaza los avisos con una marca de tiempo vieja para evitar que alguien repita uno. ```ts import { createHmac, timingSafeEqual } from "node:crypto" export function isValidWebhook(rawBody: string, timestamp: string, signature: string, secret: string) { const isRecent = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300 const expected = `sha256=${createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex")}` return ( isRecent && expected.length === signature.length && timingSafeEqual(Buffer.from(expected), Buffer.from(signature)) ) } ``` ## Responde y reintentos [#reintentos] Responde con un código 2xx en menos de 10 segundos. Cualquier otra respuesta cuenta como intento fallido, también una redirección, que no se sigue. Un intento fallido se reintenta a 1 minuto, 5 minutos, 30 minutos, 2 horas y 6 horas. Después del sexto intento, la entrega queda en **Fallido** y no se vuelve a enviar. Si la integración pierde el permiso o el webhook deja de recibir ese evento antes de un reintento, la entrega queda en **Fallido** sin enviarse. Si el webhook se pausa, sus avisos pendientes esperan y llegan cuando se reactiva, así que pueden llegar tiempo después del evento. Los avisos pueden llegar en otro orden del que ocurrieron. La URL debe utilizar `https`, no puede incluir usuario ni contraseña y debe apuntar a una dirección pública: se rechaza si resuelve a una dirección interna. Cada intento queda en la pestaña **Logs** de la integración, con el código que contestó tu sistema o **Sin respuesta**. # Asistente de IA (/docs/ai/assistant) ## Próximamente [#próximamente] El asistente de IA integrado en la plataforma se encuentra en desarrollo. Responderá preguntas sobre tu nómina, tus empleados y cómo utilizar el sistema, con base en esta documentación. # Conectores de IA (/docs/ai/mcp) Un conector de IA une tu asistente (Claude, ChatGPT u otro compatible) con la plataforma. Una vez conectado, le pides las cosas en lenguaje natural: "¿cuántos empleados activos hay por departamento?", "registra una falta para el empleado 125 el lunes", "¿qué recibos no se timbraron y por qué?". El asistente consulta y actualiza la información con **tu usuario y tus mismos permisos**: no puede ver ni hacer nada que tú no puedas hacer en la plataforma. La conexión usa el estándar MCP (Model Context Protocol), el mismo que usan estos asistentes para conectarse a otras herramientas. **Antes de empezar.** Necesitas un usuario del panel de nómina y que la organización haya terminado la [configuración inicial](/docs/getting-started#configuracion-inicial); hasta entonces, la página de conectores no está disponible. ## Obtener la URL del conector [#url-del-conector] 1. Entra a **Configuración** y, en la sección **Plataforma**, abre **Conectores de IA**. 2. Haz clic en **Copiar URL**. La URL es la misma para todos los usuarios de la plataforma; lo que cada conector puede hacer depende de quién lo autoriza. ## Conectar tu asistente [#conectar] La página **Conectores de IA** muestra estos pasos para cada asistente. ### Claude [#claude] Disponible en claude.ai y en la app de escritorio. 1. En Claude, abre **Configuración → Conectores**. 2. Selecciona **Agregar conector personalizado**. 3. Escribe el nombre de la plataforma y pega la URL del conector. 4. Selecciona **Conectar** y [autoriza el acceso](#autorizar). En las cuentas de equipo o empresa de Claude, puede ser necesario que un administrador de la cuenta agregue primero el conector para la organización. ### ChatGPT [#chatgpt] Requiere un plan de ChatGPT que permita conectores personalizados y tener activo el modo desarrollador. 1. En ChatGPT, abre **Configuración → Aplicaciones y conectores** y, en la configuración avanzada, activa el modo desarrollador. 2. Selecciona **Crear**, escribe el nombre y pega la URL del conector. Elige OAuth como autenticación. 3. [Autoriza el acceso](#autorizar). 4. Actívalo desde el menú **+** de la conversación en la que quieras usarlo. ### Claude Code [#claude-code] Para trabajar desde la terminal, ejecuta el comando que aparece en la pestaña **Claude Code** de la página (incluye la URL del conector). Después escribe `/mcp` dentro de Claude Code, selecciona el conector y autoriza el acceso en el navegador. ### Otros asistentes [#otros] Cualquier asistente compatible con servidores MCP remotos por HTTP y autenticación OAuth puede conectarse: agrega un servidor MCP remoto, pega la URL del conector y autoriza el acceso. ## Autorizar el acceso [#autorizar] Al conectar, el asistente abre la plataforma en tu navegador: 1. Si no tienes una sesión abierta, inicia sesión con tu contraseña y el código de verificación, como siempre. 2. Elige la **organización** con la que trabajará el asistente. 3. Revisa lo que autorizas y el sitio al que se enviará la autorización: debe ser el de tu asistente (por ejemplo, `claude.ai`). Si no lo es, no permitas el acceso. 4. Haz clic en **Permitir acceso**. Tu contraseña nunca se comparte con el asistente: la plataforma le entrega un acceso propio que puedes retirar en cualquier momento. Cada conexión trabaja con **una organización**; si administras varias y quieres usar el asistente en otra, conéctalo de nuevo y elígela. ## Qué puede hacer [#que-puede-hacer] El asistente tiene las mismas acciones que la plataforma, agrupadas por área: empresa y estructura, empleados, solicitudes, asistencia e incidencias, vacaciones y permisos, cálculo de nómina, timbrado, finiquitos, configuración de nómina y de asistencia, usuarios y catálogos fiscales. La sección **Qué pueden hacer los conectores de IA** de la página lista todas las acciones disponibles con tus permisos; pasa el cursor sobre el ícono de información para ver qué hace cada una. * **Consultas**: empleados, recibos, periodos, incidencias, saldos de vacaciones, el estado del timbrado y más. * **Ediciones**: dar de alta empleados, capturar incidencias, aprobar vacaciones, calcular un periodo, entre otras. * **Acciones irreversibles**, marcadas como **Pide confirmación**: cerrar una nómina, timbrar una nómina, cancelar un recibo timbrado, marcar un finiquito como definitivo o eliminar un registro. El asistente te describe el efecto y te pide confirmación antes de ejecutarlas. El asistente también puede **guiarte en la puesta en marcha**: pídele "ayúdame a configurar mi empresa paso a paso" y revisará qué falta, te pedirá los datos y los registrará contigo. En Claude, la opción **Configuración inicial guiada** del menú de prompts inicia esa conversación. Además consulta esta documentación y la referencia de la API, así que puede explicarte cómo hacer algo en la plataforma o cómo integrar otro sistema. Algunas cosas se hacen solo en la plataforma: * **Archivos**: el asistente no genera ni descarga recibos en PDF o XML, reportes de Excel ni archivos para el banco; te comparte el enlace a la pantalla donde los descargas. * **Subir archivos**, como el certificado de sello digital o las importaciones masivas. * **Crear o rotar API keys y crear webhooks**, porque mostraría la llave o el secreto en la conversación. * **Terminar la configuración inicial**: el asistente puede completar sus pasos, pero el cierre lo confirmas tú en la pantalla de configuración inicial. ## Tus conexiones [#conexiones] La sección **Conexiones** de la página **Conectores de IA** muestra los asistentes conectados a la organización en tu nombre, con la fecha de conexión y su última actividad. Para retirar el acceso de un asistente, haz clic en **Desconectar** y confirma. El cambio es inmediato: su siguiente solicitud se rechaza y, para volver a usarlo, tendrás que conectarlo de nuevo. Los cambios que haga el asistente quedan registrados a tu nombre, igual que si los hicieras tú en la plataforma. # Incidencias y conceptos (/docs/attendance/incidences) Lo que no llega por el reloj checador se captura en **Incidencias → Asistencia**, en dos pestañas: * **Incidencias**: incidencias de tiempo, medidas en días, horas u ocurrencias. Por ejemplo faltas, permisos, vacaciones, incapacidades, horas extra y retardos. * **Conceptos**: montos en pesos para un concepto de nómina, como un bono, una comisión o un descuento. Al calcular la corrida, la nómina toma todo lo registrado con fecha dentro del periodo, salvo las horas extra pendientes de autorizar o rechazadas. ## Las incidencias del periodo [#incidencias] Entra a **Incidencias → Asistencia** y abre la pestaña **Incidencias**. Elige el **Periodo** y marca a los empleados en la lista de la izquierda; la guía [Resumen de asistencia](/docs/attendance/summary#empleados) explica cómo se usan el periodo y la lista. Mientras no marques a nadie, los botones del centro te ayudan a encontrar a quienes tienen algo que revisar: **Con faltas**, **Con retardos**, **Con incapacidad**, **De vacaciones**, **Con permisos** u **Horas extra**. Con empleados marcados ves los totales del periodo por tipo y, si marcaste a uno solo, el **Calendario del periodo** con sus incidencias día por día. Debajo, **Incidencias del periodo** lista cada incidencia con su **Fecha**, **Empleado**, **Tipo**, **Cantidad**, **Origen** y **Notas**. Desde el menú de cada una puedes **Editar** o **Eliminar**. Las horas extra que todavía esperan autorización llevan la etiqueta **Por autorizar**, y las que se rechazaron, **Rechazada**. Ninguna de las dos se paga; consulta [Autorización de horas extra](#autorizacion-horas-extra). El origen **Asistencia** indica que se capturó a mano; **Reloj checador**, que la plataforma la derivó de las checadas; **Portal de colaborador**, que la registró un supervisor o que viene de una solicitud de tiempo libre aprobada. Los tipos de incidencia disponibles son fijos y se consultan en [Catálogo de asistencias](/docs/configuration/incidence-types). ## Capturar incidencias [#capturar] 1. Haz clic en **Capturar incidencias**. El botón está en las pestañas **Resumen** e **Incidencias**; la captura empieza con los empleados que tengas abiertos o marcados. 2. Cada fila es una incidencia. Elige el **Empleado** y el **Tipo**, y las fechas **Desde** y **Hasta**. Para un solo día, deja las dos iguales. 3. Para horas y ocurrencias, captura la **Cantidad**. Los tipos de día completo, como una falta, siempre llevan 1. En **Vacaciones pagadas** y **Vacaciones no pagadas** la cantidad es 1 para el día completo o 0.5 para medio día. 4. En una incapacidad, captura el **Folio**. Solo las incapacidades llevan folio. 5. Si quieres, agrega **Notas** a la fila. 6. Usa **Agregar fila** para otra incidencia. La fila nueva copia la anterior, así que solo cambias lo que difiere. 7. Haz clic en **Guardar**. Para llenar varias filas rápido, los botones junto a los encabezados **Tipo** y **Desde** copian el tipo o las fechas de la primera fila a las demás. Solo aparecen los empleados del calendario del periodo elegido, y un rango no puede pasar de 366 días. En **Vacaciones pagadas** y en los permisos, el rango solo registra los días hábiles del horario del empleado: sus días de descanso y los días festivos de ley se omiten (ver [Vacaciones](/docs/time-off)). **Guardar** queda deshabilitado mientras alguna fila esté incompleta; el aviso dice cuántas faltan. Al guardar, las filas que se registraron desaparecen de la tabla y las que no se pudieron guardar se quedan con su motivo escrito en rojo debajo de la fila, por ejemplo "El empleado ya tiene otra incidencia de día completo en esas fechas.", seguido de la fecha y el tipo de cada incidencia con la que choca. Corrígelas y vuelve a guardar. **Fechas de periodos ya pagados.** Si alguna fila cae en un periodo cerrado, la plataforma te pide confirmar una sola vez para todas esas filas. Al confirmar, la diferencia se aplica como un ajuste retroactivo en la siguiente nómina. ## Captura masiva [#captura-masiva] Para capturar muchas incidencias a la vez, por ejemplo la asistencia de toda una quincena: 1. En el menú junto a **Capturar incidencias**, elige **Captura masiva**. 2. Marca a los empleados en la lista. Todos deben pertenecer a la misma razón social. 3. En el menú junto a **Capturar incidencia**, elige **Captura masiva**. 4. Elige el rango con **Desde** y **Hasta**. El rango máximo es de 3 meses, y cada día del rango se vuelve una columna de la hoja. 5. Llena la hoja. Haz clic en **Validar** y corrige lo que marque el panel de errores. 6. Haz clic en **Publicar**. Si cierras la hoja sin publicar, la sesión se conserva y puedes retomarla o descartarla después. ## Captura de supervisores desde el portal [#supervisores] Los supervisores pueden registrar desde el portal de colaborador faltas, retardos, horas extra y días trabajados de sus reportes directos. Viene desactivado: 1. Entra a **Configuración → Asistencia → Reloj checador**. 2. En **Portal de colaborador**, activa **Supervisores capturan incidencias**. 3. Haz clic en **Guardar cambios**. Sus capturas aparecen en las incidencias del periodo con el origen **Portal de colaborador**, y se editan o eliminan como cualquier otra. Las horas extra que captura un supervisor siguen la [autorización de horas extra](#autorizacion-horas-extra). Desactivado, los supervisores solo consultan las incidencias de su equipo. La guía [Lo que ve el supervisor](/docs/employee-portal/supervisor#incidencias-del-equipo) describe la captura. ## Autorización de horas extra [#autorizacion-horas-extra] Las **Horas extra dobles** y **triples** no se pagan solo con capturarlas: quedan pendientes de autorizar hasta que las aprueban todos los niveles que tengas encendidos. Pasa así con la captura de esta pantalla, la captura del supervisor desde el portal de colaborador y la API. La hoja de la [captura masiva](#captura-masiva) no acepta horas extra: se registran con **Capturar incidencias**. Los niveles se eligen en **Configuración → Asistencia → Reloj checador**, en la sección **Autorización de horas extra**: * **Autoriza el jefe directo**: autoriza el supervisor directo del empleado. Si el empleado no tiene jefe directo, autoriza Recursos Humanos. Viene encendido. * **Autoriza RH**: Recursos Humanos las autoriza en [Solicitudes](/docs/requests#horas-extra). Viene apagado. Al menos uno debe quedar encendido. Con los dos encendidos, primero autoriza el jefe directo y después Recursos Humanos. Haz clic en **Guardar cambios** para aplicar el cambio. Los niveles nuevos aplican a las horas extra que se capturen después: las que ya estaban pendientes conservan los niveles con los que se capturaron, y la sección te dice cuántas son para que las decidas en [Solicitudes](/docs/requests#horas-extra). **El nivel de quien captura se aprueba solo.** Si capturas horas extra desde la plataforma, el paso de Recursos Humanos ya queda aprobado; si las captura el supervisor desde el portal, el del jefe directo. Las que llegan por la API no aprueban ningún nivel. Por ejemplo, con solo **Autoriza RH** encendido, las horas extra que capturas tú se pagan de inmediato, y las que captura un supervisor esperan tu autorización. Con la configuración de fábrica, solo **Autoriza el jefe directo**, lo que capturas tú para un empleado con jefe directo espera al jefe directo. El jefe directo autoriza o rechaza las horas extra de sus reportes directos desde el portal de colaborador, en el portal web o en la app. Tú también puedes aprobarlas o rechazarlas por él en [Solicitudes](/docs/requests#horas-extra). La guía [Lo que ve el supervisor](/docs/employee-portal/supervisor#horas-extra) explica cómo las decide. **Editar las horas o el tipo vuelve a pedir autorización.** Si cambias la cantidad o pasas de dobles a triples, el registro vuelve a quedar pendiente de autorizar y empieza otra vez la cadena. Cambiar solo las notas no la reinicia. Si eran horas extra ya autorizadas en un periodo cerrado y la nueva autorización queda pendiente de alguien más, la confirmación del ajuste retroactivo te avisa que la siguiente nómina descontará todas las horas ya pagadas, no solo la diferencia, hasta que se autoricen de nuevo. **Unas horas extra rechazadas se corrigen editándolas.** Si capturas horas extra sin vincular checadas para un día en que el empleado ya tiene unas rechazadas del mismo tipo, también sin checadas vinculadas, la captura se rechaza y no se registra nada: cambia las horas del registro rechazado para volver a pedir autorización, o elimínalo y vuelve a capturar. Lo mismo pasa con la captura del supervisor desde el portal y con la API. Mientras están pendientes o si se rechazan, la nómina no las paga y avisa cuántas quedan por autorizar; consulta [Abrir y calcular un periodo](/docs/payroll/run#horas-extra-por-autorizar). Si se autorizan después de cerrar el periodo, entran como ajuste retroactivo en la siguiente nómina. ## Los conceptos del periodo [#conceptos] Entra a **Incidencias → Asistencia** y abre la pestaña **Conceptos**. Elige el **Periodo** y marca a los empleados en la lista de la izquierda; junto a cada uno ves cuántos montos tiene capturados en el periodo. Mientras no marques a nadie, los botones del centro te ayudan a encontrarlos: **Con percepciones**, **Con deducciones**, **Con informativos** y **Pendientes de procesar**, que reúne a quienes tienen montos que todavía no paga una nómina cerrada. Con empleados marcados ves los totales del periodo: **Percepciones**, **Deducciones**, **Capturas** y **Sin procesar**, más **Informativos** cuando los hay. Debajo, **Conceptos del periodo** lista cada monto con su **Fecha**, **Empleado**, **Concepto**, **Monto**, **Origen** y **Estatus**: **Procesada** si una nómina cerrada ya lo pagó y **Pendiente** si todavía no. Si marcaste a un solo empleado, también ves sus **Notas**. Desde el menú de cada monto puedes **Editar** o **Eliminar**. ![Pestaña Conceptos con cinco empleados marcados, sus totales y los conceptos del periodo](/images/guides/attendance/concepts-period.webp) ## Capturar conceptos [#capturar-conceptos] 1. En la pestaña **Conceptos**, haz clic en **Capturar conceptos**. La captura empieza con los empleados que tengas marcados. 2. Cada fila es un monto. Elige el **Empleado** y el **Concepto**. La lista agrupa los conceptos en **Percepciones**, **Deducciones** e **Informativos**, y muestra todos los conceptos activos de captura manual, estén o no en el contrato del empleado, más los conceptos de fórmula que su contrato tiene en **Personalizado** con **Captura manual**. 3. Revisa la **Fecha de aplicación**. El monto se paga o se descuenta en el periodo de nómina que incluye esa fecha. 4. Captura el **Monto** en pesos, con máximo dos decimales. Si presionas Enter en el monto de la última fila, se agrega otra con el mismo empleado, concepto y fecha. 5. Si quieres, agrega **Notas** a la fila. 6. Haz clic en **Guardar**. ![Diálogo Capturar conceptos con un bono para cinco empleados](/images/guides/attendance/concepts-capture.webp) Para llenar varias filas rápido, los botones junto a los encabezados **Concepto**, **Fecha** y **Monto** copian el valor de la primera fila a las filas que todavía no lo tienen. **Agregar fila** agrega una fila con el empleado, concepto y fecha de la anterior, sin monto ni notas. Varios montos del mismo empleado y concepto se suman. Si alguna fila está incompleta, el pie del diálogo dice cuántas faltan y, al hacer clic en **Guardar**, sus celdas se marcan en rojo. Al guardar, las filas que se registraron desaparecen de la tabla y las que no se pudieron guardar se quedan con su motivo escrito en rojo debajo de la fila, por ejemplo "El concepto no se puede capturar para este empleado. Revisa el concepto en el catálogo de conceptos de nómina." cuando el concepto se desactivó o ya no es de captura manual. Corrígelas y vuelve a guardar. Si alguna fila cae en un periodo ya pagado, la plataforma te pide confirmar una sola vez, como en las incidencias; si cancelas, vuelves a la tabla con lo que capturaste. Los créditos (INFONAVIT, FONACOT, pensión alimenticia, préstamos) y los descuentos por días no se capturan aquí: se calculan solos a partir de los créditos del empleado y de sus incidencias. La excepción es **Crédito INFONAVIT pendiente**, el importe que no se descontó entre el inicio del aviso de retención y el periodo en que se registró el crédito: ese sí se captura aquí, como un descuento único en la nómina donde se aplica. ## Captura masiva de conceptos [#captura-masiva-conceptos] En el menú junto a **Capturar conceptos**, elige **Captura masiva**. Ahí marcas a los empleados y tienes dos opciones: * **Capturar concepto** registra los mismos conceptos y montos para todos los marcados, con una sola fecha de aplicación. Solo aparecen los conceptos de captura manual que tienen en común; si no comparten ninguno, el botón queda deshabilitado. * **Captura masiva**, en el menú junto a ese botón, abre una hoja para capturar montos de muchos empleados a la vez. Esa misma pantalla tiene siempre el botón **Captura rápida**, que no necesita empleados marcados. ## Captura rápida [#captura-rapida] **Captura rápida** abre una hoja en blanco con una fila por monto, pensada para pegar una lista desde Excel o escribirla a mano. Cada fila lleva **Número de empleado**, **Clave del concepto** (por ejemplo P067), **Fecha de aplicación**, **Importe** y **Referencia**, que se guarda como la nota del monto. Si tu organización tiene el número interno encendido, también aparece **Número interno**, y puedes identificar al empleado con cualquiera de los dos. Al escribir el número y la clave, la hoja muestra el nombre del empleado y del concepto. 1. En la pestaña **Conceptos**, elige **Captura masiva** en el menú junto a **Capturar conceptos** y haz clic en **Captura rápida**. 2. Escribe las filas o pega un bloque copiado de Excel en la celda donde debe empezar. La hoja empieza con 50 filas; si vas a pegar más, haz clic en **Agregar filas** antes. Un pegado que no cabe o que trae un valor inválido no escribe nada y te dice por qué en el panel de errores. 3. Haz clic en **Validar**. Cada fila se revisa contra tus empleados y conceptos: un número de empleado que no existe, un empleado dado de baja, una clave que no existe o que el empleado no puede recibir, un importe en cero o con más de dos decimales, o una fecha que falta aparecen en el panel de errores, fila por fila. Las filas vacías no se toman en cuenta. Si dos filas repiten empleado, concepto y fecha, el panel lo avisa, pero no impide publicar: se registran todas. 4. Con todo correcto, haz clic en **Publicar**. Todas las filas se registran a la vez, como si las hubieras capturado una por una. Si alguna fecha cae en un periodo ya pagado, la plataforma te pide confirmar el ajuste retroactivo. Si sales sin publicar, la hoja se conserva. La siguiente vez que hagas clic en **Captura rápida**, eliges **Reanudar** para seguir con ella o **Descartar** para empezar con una hoja vacía. ## Registro de conceptos [#registro-conceptos] Si tu organización todavía no tiene periodos de nómina, la pestaña **Conceptos** muestra **Ver registro**. El **Registro de conceptos** muestra cada monto capturado con su empleado, razón social, concepto, monto, fecha de aplicación y origen. # Asistencia (/docs/attendance) La nómina de cada periodo toma las incidencias de asistencia registradas: faltas, retardos, permisos, vacaciones, incapacidades, horas extra y días trabajados. Algunas llegan solas desde el reloj checador y otras las captura Recursos Humanos en **Incidencias → Asistencia**. Esta sección explica cómo se llenan y cómo se revisan. ## Cómo fluye la asistencia hacia la nómina [#cómo-fluye-la-asistencia-hacia-la-nómina] 1. **Horarios.** Cada empleado tiene un horario o una rotación que dice a qué hora entra y sale cada día. Si no tiene uno asignado, aplica el horario predeterminado de la organización. 2. **Checadas.** Las entradas y salidas llegan desde un reloj checador, desde la captura de la plataforma o desde la app, al escanear un código QR. 3. **Retardos.** Si activas la derivación, la plataforma compara cada checada contra el horario del empleado y registra el retardo por su cuenta. 4. **Faltas y demás incidencias.** Las faltas, permisos, vacaciones, incapacidades y horas extra se capturan a mano. Si activas **Derivar faltas**, un día laborable que terminó sin ninguna checada se registra como falta por su cuenta. Las horas extra solo se pagan cuando las autorizan el jefe directo, Recursos Humanos o los dos, según la configuración; consulta [Autorización de horas extra](/docs/attendance/incidences#autorizacion-horas-extra). 5. **Solicitudes.** Los colaboradores piden desde el portal tiempo libre, correcciones de checada y justificaciones de retardo. Se resuelven en **Incidencias → Solicitudes**, donde también se autorizan las horas extra; consulta [Solicitudes](/docs/requests). 6. **Cálculo.** Al calcular la corrida, la nómina toma todas las incidencias con fecha dentro del periodo, salvo las horas extra pendientes de autorizar o rechazadas. **Las faltas solo se derivan si lo activas.** Un día sin checadas no siempre prueba que el empleado faltó: puede tratarse de un permiso que nadie capturó o de una falla del reloj. Por eso **Derivar faltas** viene apagada y, mientras lo esté, la plataforma no registra una falta por su cuenta. La página [Retardos y faltas](/docs/attendance/lateness#derivar-faltas) explica cómo funciona y cómo capturarlas a mano. ## La pantalla Asistencia [#pantalla] **Incidencias → Asistencia** tiene tres pestañas: * **Resumen**: la asistencia del periodo de todo el equipo, día por día, y la de cada empleado. * **Incidencias**: las incidencias registradas del periodo y la captura de nuevas. * **Checadas**: las checadas de cada día, su revisión y su captura. Las tres trabajan sobre una **Razón social** y un **Periodo** de nómina, que se eligen arriba a la izquierda. Al entrar se elige el periodo de hoy; las flechas junto al selector pasan al periodo anterior o al siguiente. La lista de la izquierda muestra a los empleados del calendario de ese periodo; la búsqueda por nombre o número de empleado y **Filtros** la acotan. La guía [Resumen de asistencia](/docs/attendance/summary) describe cómo se usan. Si la organización todavía no tiene un calendario de nómina, la pantalla lo indica: la asistencia se revisa por periodo, así que primero hay que crear uno. ## En esta sección [#en-esta-sección] * [Horarios y rotaciones](/docs/attendance/schedules): crear horarios semanales, turnos nocturnos, rotaciones y asignarlos a los empleados. * [Resumen de asistencia](/docs/attendance/summary): el periodo, la lista de empleados, la cuadrícula del equipo y el detalle de cada día. * [Checadas](/docs/attendance/time-clock): de dónde llegan las checadas, cómo capturarlas, cómo configurar los códigos QR y cómo revisarlas. * [Retardos y faltas](/docs/attendance/lateness): la derivación automática de retardos y faltas, y la captura de faltas. * [Incidencias y conceptos](/docs/attendance/incidences): capturar incidencias de tiempo y montos como bonos o descuentos. El catálogo de tipos de incidencia se describe en [Catálogo de asistencias](/docs/configuration/incidence-types). Las vacaciones tienen su propia guía en [Vacaciones](/docs/time-off). # Retardos y faltas (/docs/attendance/lateness) La plataforma puede registrar los retardos por su cuenta: compara cada checada contra el horario del empleado y, si llegó tarde, anota el retardo en la asistencia del empleado. También puede registrar una falta en cada día laborable que terminó sin ninguna checada, si activas **Derivar faltas**; si no, las faltas se capturan a mano. ## Activar la derivación de retardos [#activar-la-derivación-de-retardos] La derivación viene apagada. Para activarla: 1. Entra a **Configuración → Asistencia → Reloj checador**. 2. Enciende **Derivar retardos**. 3. Si también quieres registrar retardos al regresar de comer, enciende **Derivar retardos por comida**. Esta opción requiere que **Derivar retardos** esté encendido. 4. Haz clic en **Guardar cambios** y confirma con **Activar**. ![Configuración del reloj checador con la derivación de retardos encendida](/images/guides/attendance/time-clock-settings.webp) **La regla aplica desde la activación.** A partir de ese momento, cada checada que entra se compara contra el horario del empleado. Los días que ya estaban capturados no se recalculan; si necesitas los retardos anteriores, captúralos a mano con **Capturar incidencias** en **Incidencias → Asistencia**. ![Diálogo para confirmar la activación de la derivación de retardos](/images/guides/attendance/enable-lateness-confirm.webp) ## Cómo se decide un retardo [#cómo-se-decide-un-retardo] La plataforma revisa cada día laborable del empleado según su horario: * **Entrada.** Toma la primera checada del día que no sea una salida. Si llega después de la hora de entrada más la tolerancia, registra un **Retardo**. * **Regreso de comer.** Si activaste los retardos por comida y el regreso llega después de la hora de regreso más la tolerancia de comida, registra otro **Retardo**. Un mismo día puede tener los dos. * **Turnos nocturnos.** Las checadas de la madrugada que pertenecen al turno de la noche anterior no cuentan como entrada del día nuevo. * **Salida olvidada.** Una primera checada a la hora de salida o después se toma como la salida que faltó del turno anterior, no como una entrada tarde. * **Días de descanso y festivos.** Un día que no es laborable en el horario nunca genera retardo, y un día festivo tampoco, aunque el horario lo marque como laborable. **Salir antes no es retardo.** La plataforma solo evalúa la llegada y el regreso de comer. Una salida anticipada no genera ninguna incidencia. **Lo capturado a mano gana.** Si el día ya tiene una incidencia de día completo, como una falta, una incapacidad o vacaciones, la plataforma no deriva retardo aunque haya checadas. Un retardo capturado a mano también prevalece sobre el que derivaría la plataforma. Cada retardo derivado aparece en las incidencias del periodo con el origen **Reloj checador**. Esos registros no se pueden editar: los genera la plataforma a partir de las checadas. Si una checada está mal, elimínala desde el detalle del día en la pestaña **Checadas** y el retardo del día se actualiza. Consulta [Checadas](/docs/attendance/time-clock). **El colaborador puede justificar un retardo.** Desde el portal de colaborador, el colaborador pide justificar un retardo derivado del mes en curso o del anterior. Si su jefe directo o Recursos Humanos la aprueba, el retardo se elimina de la asistencia y la plataforma no lo vuelve a derivar. Consulta [Solicitudes](/docs/requests). Editar un horario o una rotación tampoco recalcula el pasado: los cambios aplican a los retardos que se deriven de ahí en adelante. ## Derivar faltas [#derivar-faltas] **Derivar faltas** viene apagada, porque un día sin checadas no siempre es una falta: puede ser un permiso que nadie capturó, una comisión fuera de la oficina o un reloj que dejó de enviar datos. Con la opción apagada, un día laborable sin checadas queda como incompleto hasta que lo revises. Para activarla: 1. Entra a **Configuración → Asistencia → Reloj checador**. 2. Enciende **Derivar faltas**. Es independiente de **Derivar retardos**. 3. Haz clic en **Guardar cambios** y confirma con **Activar**. Con la opción encendida, cada noche la plataforma revisa el día que acaba de terminar para cada empleado activo, en su zona horaria: * **Sin ninguna checada.** Un día laborable sin checadas ni tiempo libre se registra como **Falta injustificada**, con el origen **Reloj checador**. * **Con al menos una checada.** El día sigue incompleto aunque le falten checadas: no se vuelve falta. * **Tiempo libre.** Vacaciones, permisos, incapacidades, descansos y festivos nunca se vuelven falta. Un día con una solicitud de tiempo libre pendiente de decidir tampoco. * **Lo capturado a mano gana.** Si el día ya tiene una incidencia capturada a mano, la plataforma no registra la falta. **La falta se retira sola.** Si después llega una checada de ese día, desde la app, la captura de la plataforma o un reloj conectado por la API, la falta derivada se elimina. Lo mismo pasa si capturas vacaciones, un permiso, una incapacidad o una falta a mano en ese día, o si se aprueba una solicitud de tiempo libre que lo cubre: lo capturado ocupa su lugar. Si el periodo ya está cerrado, el cambio entra como ajuste retroactivo, y una corrida abierta se recalcula sola. Si una nómina cerrada ya descontó esa falta, también se elimina cuando llega la checada y queda un ajuste retroactivo pendiente para devolver lo descontado. Para capturar vacaciones, un permiso o una incapacidad en un día cuya falta ya se pagó, primero elimina la falta. **La regla aplica desde la activación.** Solo se vuelven falta los días que terminan después de encender la opción; los días anteriores siguen incompletos. Si necesitas esas faltas, captúralas a mano como **Falta injustificada** con **Capturar incidencias**, en **Incidencias → Asistencia**. El paso a paso está en [Incidencias y conceptos](/docs/attendance/incidences#capturar). Al calcular y al cerrar una nómina, la corrida avisa cuántos días incompletos quedan en el periodo; consulta [Días incompletos](/docs/payroll/run#dias-incompletos). # Horarios y rotaciones (/docs/attendance/schedules) El horario le dice a la plataforma qué días trabaja un empleado, a qué hora entra y sale, y cuántos minutos de tolerancia tiene. Se usa para acomodar las checadas de cada día y, si activas la derivación, para registrar retardos. Los horarios se administran en **Configuración → Asistencia → Horarios**, que tiene dos pestañas: **Horarios** y **Rotaciones**. ![Pantalla de horarios sin horarios creados](/images/guides/attendance/work-schedules-empty.webp) ## Crear un horario [#crear-un-horario] 1. En la pestaña **Horarios**, haz clic en **Crear horario**. 2. Escribe el **Nombre** y, si quieres, una **Descripción**. 3. Marca los días laborables. Los días sin marcar son de descanso. 4. En cada día marcado, captura la **Entrada**, la **Salida** y la **Tolerancia (min)**. La tolerancia viene en 10 minutos; puedes cambiarla por día. 5. Si el día incluye comida, haz clic en **Comida** y captura la **Salida a comer**, el **Regreso** y su **Tolerancia**. 6. Haz clic en **Crear horario**. ![Formulario para crear un horario de lunes a viernes](/images/guides/attendance/work-schedule-form.webp) La tolerancia son los minutos de gracia después de la hora de entrada antes de que la checada cuente como retardo. Con entrada a las 09:00 y 10 minutos de tolerancia, una checada a las 09:08 llega a tiempo y una a las 09:25 es retardo. **Comida.** La comida es opcional y se define por día. Debe quedar dentro del turno: la salida a comer después de la entrada y el regreso antes de la salida. Su tolerancia aplica al regreso; si la dejas vacía, se usa la tolerancia del día. **Turnos nocturnos.** Si la salida es menor que la entrada, por ejemplo de 22:00 a 06:00, la plataforma entiende que el turno termina al día siguiente. Las checadas de la madrugada se cuentan como parte del turno que empezó la noche anterior, no como la entrada del día nuevo. El horario aparece en la tabla con sus días laborables, su horario y cuántos empleados lo tienen asignado. ![Tabla de horarios con el horario Oficina lunes a viernes](/images/guides/attendance/work-schedules-table.webp) **Editar un horario no recalcula el pasado.** Los cambios solo aplican a los retardos que se deriven de ahí en adelante; los días ya procesados se quedan como estaban. ## Crear una rotación [#crear-una-rotación] Una rotación encadena horarios que ya existen en un ciclo que se repite, para turnos que cambian cada cierta cantidad de semanas. ![Pestaña Rotaciones sin rotaciones creadas](/images/guides/attendance/rotations-tab.webp) 1. En la pestaña **Rotaciones**, haz clic en **Crear rotación**. 2. Escribe el **Nombre** y, si quieres, una **Descripción**. 3. Elige el **Inicio del ciclo (lunes)**. El ciclo siempre arranca en lunes. 4. Agrega los pasos con **Agregar paso**. En cada uno elige el **Horario** y cuántas **Semanas** dura, de 1 a 52. 5. Revisa la duración total del ciclo, que se muestra debajo de los pasos, y guarda. Una rotación necesita al menos dos pasos, y dos pasos seguidos no pueden usar el mismo horario: en ese caso aumenta las semanas del primero. Al terminar el último paso, el ciclo vuelve a empezar. ## Asignar un horario o una rotación [#asignar-un-horario-o-una-rotación] El horario se asigna a cada empleado. Hay dos formas de hacerlo: * **Desde el horario o la rotación.** En el menú de opciones de la fila, elige **Asignar empleados**. Marca a los empleados y haz clic en **Guardar**. Antes de aplicar, un resumen te muestra quiénes pasan a este horario y quiénes salen de él; quienes salen se quedan con el horario predeterminado. * **Desde el empleado.** En el alta o la edición del empleado, sección **Horario**, elige un horario o una rotación en el campo **Horario**. Al asignar una rotación, indica en **Inicia en el paso** en qué paso arranca cada grupo. Así dos cuadrillas pueden rotar escalonadas con la misma rotación. El paso elegido solo aplica a los empleados que agregas en ese momento; quienes ya estaban en la rotación conservan el suyo. ## Horario predeterminado [#horario-predeterminado] El horario predeterminado aplica a todo empleado que no tenga un horario ni una rotación asignados. Para elegirlo: * Entra a **Configuración → Asistencia → Reloj checador** y selecciona el **Horario default**, luego haz clic en **Guardar cambios**. * O, en la tabla de horarios, usa **Marcar como default** en el menú de opciones de la fila. ![Configuración del reloj checador con el horario default](/images/guides/attendance/time-clock-settings.webp) En la misma pantalla se activa la derivación de retardos, que se explica en [Retardos y faltas](/docs/attendance/lateness). # Resumen de asistencia (/docs/attendance/summary) La pestaña **Resumen** de **Incidencias → Asistencia** muestra lo que pasó en el periodo: quién asistió, quién llegó tarde, quién faltó y qué días tienen checadas incompletas. Es el lugar para revisar la asistencia antes de calcular la nómina. ## Elegir la razón social y el periodo [#periodo] Arriba a la izquierda están los selectores **Razón social** y **Periodo**. Al entrar se elige el periodo de nómina que incluye el día de hoy, y la razón social es la de ese periodo. Al cambiar de razón social se elige su periodo actual. El selector **Periodo** muestra solo los calendarios de la razón social elegida, y las flechas a los lados pasan al **Periodo anterior** o al **Periodo siguiente**. La razón social y el periodo que elijas se mantienen al cambiar entre **Resumen**, **Incidencias** y **Checadas**. Cada periodo pertenece a un calendario de nómina, así que la pantalla muestra solo a los empleados de ese calendario. También aparecen los empleados **Pendientes** de alta, con sus checadas; si un empleado no aparece, revisa que tenga asignado el calendario de nómina del periodo y una **Fecha de ingreso** igual o anterior al fin del periodo. ## La lista de empleados [#empleados] La lista de la izquierda tiene a los empleados del periodo. Escribe en la búsqueda, arriba a la derecha, para encontrar a alguien por nombre o número de empleado; **⌘F** la activa desde cualquier parte de la pantalla. * **Todo el equipo** muestra la cuadrícula del equipo completo. * Haz clic en un empleado para ver su asistencia del periodo. En **Incidencias** y **Checadas** la lista tiene casillas: marca a uno o varios empleados para ver lo suyo, o usa **Seleccionar todos**. Puedes marcar hasta 100 empleados uno por uno; **Seleccionar todos** no tiene límite. Mientras no marques a nadie, la pantalla ofrece botones con los grupos que conviene revisar, por ejemplo los empleados **Con faltas** o con checadas **Incompletas**, con cuántos hay en cada uno. Un clic marca a todo el grupo. ## El equipo completo [#equipo] Con **Todo el equipo**, arriba aparece **Por revisar** con cuatro indicadores del periodo: * **Checadas incompletas**, **Faltas** y **Retardos**. Un clic en cualquiera filtra la cuadrícula a los empleados que los tienen. * **Correcciones pendientes**, que lleva a [Solicitudes](/docs/requests). Debajo, la barra **Listos para nómina** muestra cuántos empleados no tienen días por revisar: checadas incompletas, [checadas en un día de tiempo libre](#tiempo-libre) sin resolver ni correcciones pendientes. Los empleados **Pendientes** de alta no cuentan, porque no entran a la nómina hasta que se dan de alta. La cuadrícula tiene una fila por empleado y una columna por día del periodo. Cada día lleva una letra: **R** retardo, **F** falta, **C** checadas incompletas, **V** vacaciones, **I** incapacidad, **P** permiso y **FE** festivo; un punto indica asistencia, y los descansos y los días sin registro quedan en blanco. Pasa el cursor sobre un día para ver su descripción. Los días de tiempo libre (vacaciones, permisos, incapacidades, descansos y festivos) nunca cuentan como checadas incompletas ni como falta, y un festivo o un descanso con checadas conserva su letra o su espacio en blanco: no se muestra como asistencia. Al final de la fila están los totales del periodo: **F** faltas, **R** retardos y **HE** horas extra. Haz clic en una fila para abrir la asistencia de ese empleado. Su menú de opciones ofrece **Ver asistencia del empleado** y **Ver incidencias del periodo**. Para acotar la cuadrícula y la lista, usa **Filtros**: **Sucursal** y **Departamento** muestran solo a sus empleados, y **Mostrar** deja solo a los empleados **Con checadas incompletas**, **Con faltas** o **Con retardos**. Puedes combinarlos con la búsqueda. Cada filtro activo aparece como una etiqueta que puedes cambiar o quitar, y **Eliminar filtros** los quita todos. **Sucursal** y **Departamento** se mantienen mientras la pestaña del navegador siga abierta. **Por revisar** y **Listos para nómina** siempre cuentan al equipo completo del periodo. ## La asistencia de un empleado [#empleado] Al elegir a un empleado ves su tarjeta del periodo: * El encabezado con su nombre, su horario y las flechas **Empleado anterior** y **Empleado siguiente** para recorrer la lista sin volver a ella. * Los indicadores **Horas trabajadas**, **Días asistidos**, **Faltas**, **Retardos**, **Horas extra** y **Por revisar**. * La barra **Horas del periodo**, que separa las horas trabajadas en horario, las de días sin cierre y las de faltas. * La lista de días con su **Horario**, **Entrada**, **Comida**, **Regreso**, **Salida**, **Horas** e **Incidencias**. * **Se envía a la nómina del periodo**: lo que la nómina va a tomar, agrupado en **Descuentan** (faltas, retardos y ausencias sin goce), **Se pagan sin asistir** (incapacidades, vacaciones y permisos con goce) y **Se pagan extra** (horas extra, domingos, festivos y descansos trabajados). Las horas extra solo cuentan aquí cuando están autorizadas, igual que en la nómina; las pendientes de autorizar o rechazadas no aparecen. **Agregar checada** abre la captura de checadas para ese empleado. La guía [Checadas](/docs/attendance/time-clock#capturar) explica cómo funciona. ## El detalle de un día [#dia] Haz clic en un día de la lista para abrir su detalle: * **Jornada**: una línea de tiempo del día contra su horario. * **Checadas**: cada checada con su hora y su origen. En las checadas por QR, **Ver ubicación** muestra dónde se registraron y **Ver dispositivo** con qué teléfono, con sus avisos en ámbar; la guía [Checadas](/docs/attendance/time-clock) explica cada aviso. * **Solicitud de corrección**: si el colaborador pidió agregar una checada de ese día, aparece con su motivo y los botones **Aprobar corrección** y **Rechazar**. * **Incidencias del día**: lo registrado ese día, marcado como **Derivada** cuando la calculó la plataforma o **Capturada** cuando se capturó a mano. Al pie del detalle, **Agregar checada** y **Incidencia** capturan sobre ese mismo día. ## Checadas en un día de tiempo libre [#tiempo-libre] Si el empleado checa en un día de tiempo libre, el día lleva una marca en la lista de días y en su detalle: **Checada en vacaciones**, **Checada en permiso**, **Checada en incapacidad**, **Festivo trabajado** o **Descanso trabajado**. Mientras no se resuelve, el día cuenta en **Por revisar**: * **Festivo trabajado** o **Descanso trabajado**: captura la incidencia **Día festivo trabajado** o **Descanso semanal trabajado** para pagarlo. La marca se queda, pero el día ya no está por revisar. * **Checada en vacaciones**, **en permiso** o **en incapacidad**: si el empleado sí trabajó, elimina el tiempo libre; si la checada sobra, elimínala desde [Checadas](/docs/attendance/time-clock#revisar). Un día con medio día de tiempo libre no lleva marca. Para que la app no acepte checadas en vacaciones, permisos o incapacidades, consulta [Checadas en vacaciones, permisos o incapacidad](/docs/attendance/time-clock#checadas-en-tiempo-libre). ## Capturar incidencias desde el Resumen [#capturar-incidencias-desde-el-resumen] El botón **Capturar incidencias** de la barra superior abre la captura de incidencias. Si tienes a un empleado abierto, la captura empieza con él. El paso a paso está en [Incidencias y conceptos](/docs/attendance/incidences#capturar). # Checadas (/docs/attendance/time-clock) Una checada es un registro de hora de un empleado: su entrada, su salida a comer, su regreso o su salida. La plataforma las acomoda por día contra el horario del empleado y, si activas la derivación, las usa para registrar retardos. ## De dónde llegan las checadas [#de-dónde-llegan-las-checadas] Cada checada guarda su origen, que ves en el detalle de cada día. Hay cuatro: * **Reloj checador.** Checadas enviadas por un reloj checador físico o por un sistema externo a través de la API pública de la plataforma. La conexión la configura tu equipo técnico o el proveedor del reloj; la sección [Desarrolladores](/docs/developers) tiene lo que necesitan. * **Captura manual.** Checadas que Recursos Humanos captura en la plataforma, una por una o pegando el archivo que exporta un reloj que no está conectado. * **Escaneo QR.** Checadas que el colaborador registra desde la app al escanear el código QR de una ubicación. Puedes hacer que la app no acepte checadas en días de vacaciones, permisos o incapacidad; consulta [Checadas en vacaciones, permisos o incapacidad](#checadas-en-tiempo-libre). * **Corrección aprobada.** Checadas que el colaborador solicitó desde el portal y que aprobó su jefe directo o Recursos Humanos. La guía [Lo que ve el colaborador](/docs/employee-portal/worker#correcciones) explica cómo se solicitan. ## Revisar las checadas [#revisar] Entra a **Incidencias → Asistencia** y abre la pestaña **Checadas**. Elige el **Periodo** y marca a los empleados en la lista de la izquierda; la guía [Resumen de asistencia](/docs/attendance/summary#empleados) explica cómo se usan. Mientras no marques a nadie, los botones del centro marcan de un clic a los empleados con checadas **Incompletas** o **Sin clasificar**. Con empleados marcados ves los totales del periodo: **Días completos**, **Por revisar**, **Horas trabajadas** y **Horas extra**. Debajo aparece cada día con su **Jornada**, sus **Horas** y su **Estatus**: **Falta**, **Incompleto** o **Sin clasificar** cuando algo requiere atención. Un día de vacaciones, permiso, incapacidad, descanso o festivo nunca aparece como **Incompleto** ni como **Falta**, ni entra en el botón **Incompletas**. Haz clic en **Ver checadas del día** para ver todas sus checadas, su origen y, en las checadas por QR, dónde se registraron. El mapa muestra la ubicación del teléfono junto con el alcance que tenía el código al momento de la checada: el radio alrededor del punto actual del código, los municipios o estados elegidos, o todo México cuando el alcance era País o la checada no estaba atada a ubicación. Si después cambias el radio, los municipios, los estados o la tolerancia, las checadas anteriores conservan los que se aplicaron. Si el día generó un retardo, ahí mismo ves cuál y en qué checada. **Checadas en un día de tiempo libre.** Si el empleado checó en un día de tiempo libre, el encabezado del detalle muestra la marca **Checada en vacaciones**, **Checada en permiso**, **Checada en incapacidad**, **Festivo trabajado** o **Descanso trabajado**. Pasa el cursor sobre ella para ver cómo resolverla. Mientras está en ámbar, el día cuenta en **Por revisar**: un festivo o un descanso trabajado se resuelve capturando la incidencia **Día festivo trabajado** o **Descanso semanal trabajado**, y una checada en vacaciones, permiso o incapacidad, eliminando el tiempo libre si el empleado sí trabajó o eliminando la checada si sobra. Un día con medio día de tiempo libre no lleva marca. **Dispositivo de la checada.** En las checadas por QR, el ícono del teléfono muestra con qué dispositivo se registró, por ejemplo "iPhone 13 · iOS 18.2 · app 1.4.0", y debajo la información completa que reportó la app: marca, modelo, sistema, versión de la app, identificador de instalación, red y operador. El ícono se pinta en ámbar cuando hay algo que revisar: * **Dispositivo distinto**: el teléfono no es el de la checada anterior del mismo colaborador. Puede ser el mismo teléfono si el colaborador borró la app y la volvió a instalar en un iPhone, o si restableció de fábrica un Android: en los dos casos el teléfono se identifica como uno nuevo. * **Ubicación simulada**, **Ubicación aproximada** o **Precisión baja**: la ubicación reportada no es confiable. * **Teléfono con root/jailbreak** o **Emulador**. Estos avisos sirven para auditar las checadas: ninguno rechaza la checada ni la cuenta en **Por revisar**, y cambiar de teléfono nunca impide checar. Las checadas hechas desde versiones anteriores de la app muestran **No se encontró información del dispositivo.** **Las lecturas repetidas se agrupan.** Si el reloj registra al mismo empleado varias veces en menos de 3 minutos, la plataforma toma solo la primera y deja las demás como checadas sin clasificar. Las checadas que no encajan en ningún momento del horario, como las de un día de descanso, también quedan sin clasificar, y el día lleva además la marca **Descanso trabajado**. Si una checada está mal, puedes eliminarla con **Eliminar checada** desde el detalle del día. Escribe el **Motivo** y, si tienes un respaldo (un correo, una foto), adjúntalo en **Evidencia**. Al eliminarla, la plataforma actualiza el retardo derivado de ese día. **Cambios manuales.** El detalle del día lista, del más reciente al más antiguo, cada checada capturada o eliminada a mano: si se capturó o se eliminó, la hora de la checada, quién lo hizo, hace cuánto, el motivo y los archivos de evidencia, que puedes abrir desde ahí. Pasa el cursor sobre la foto de quien hizo el cambio para ver su nombre y la fecha y hora exactas. El registro se conserva aunque la checada ya no exista. Para entregar a una inspección las checadas, los cambios y sus evidencias de varios empleados en un solo archivo, descarga el [Registro de jornada para inspección](/docs/payroll/reports#registro-de-jornada). **Registro de checadas.** **Ver registro** muestra cada día con checadas de todos los empleados, del más reciente al más antiguo. En **Filtros** lo acotas, entre otros, por **Razón social**, **Calendario** y **Periodo**. Con **Checadas incompletas** y un periodo de hasta 62 días, la lista incluye también los días laborables que ya terminaron sin ninguna checada; con un periodo más largo, sin fecha de inicio o con un filtro de **Origen**, solo los días con alguna checada. El aviso **Días incompletos** de la nómina abre este registro ya filtrado. **Exportar como Excel**, en el menú junto a **Capturar checadas**, descarga las checadas de los empleados marcados o, si no marcaste a nadie, de todos los que coinciden con la búsqueda y los filtros. ## Capturar checadas [#capturar] 1. En la pestaña **Checadas**, haz clic en **Capturar checadas**. La captura empieza con los empleados que tengas marcados. También puedes abrirla con **Agregar checada** desde la asistencia de un empleado o el detalle de un día. 2. Cada fila es un día de un empleado. Elige el **Empleado** y la **Fecha**. 3. Escribe las horas que tengas de **Entrada**, **Salida a comer**, **Regreso de comer** y **Salida**. Cada fila necesita al menos una hora. 4. Usa **Agregar fila** para otro día u otro empleado. 5. Escribe el **Motivo** de la captura, por ejemplo que falló el código QR. Es obligatorio y aplica a todas las filas. 6. Si tienes un respaldo, adjúntalo en **Evidencia**: hasta 10 archivos PDF, imagen, Word o Excel de hasta 20 MB cada uno. 7. Haz clic en **Guardar**. Los botones junto a los encabezados copian el empleado, la fecha o una hora de la primera fila a las demás. Solo aparecen los empleados del calendario del periodo elegido, no se aceptan horas que todavía no ocurren y una captura admite hasta 200 checadas. Las checadas se agregan a las que el día ya tenía; no las reemplazan. Una hora que ya existe se marca como **Duplicada** y no se vuelve a registrar, sin detener las demás. Al guardar, la plataforma vuelve a calcular los retardos de cada día capturado. **Fechas de periodos ya pagados.** Si alguna checada cae en un periodo cerrado, la plataforma te pide confirmar una sola vez antes de guardar. Al confirmar, la diferencia se aplica como un ajuste retroactivo en la siguiente nómina. ## Captura masiva [#captura-masiva] Para pegar el archivo exportado de un reloj checador: 1. En el menú junto a **Capturar checadas**, elige **Captura masiva**. 2. Elige las **Razones sociales** y el rango de fechas con **Desde** y **Hasta**. El rango máximo es de 31 días. La sesión incluye a todos los empleados activos o pendientes de esas razones sociales. 3. En la hoja, cada fila es una checada. Pega el archivo exportado del reloj checador (Ctrl+V) o agrega las filas a mano. 4. Haz clic en **Validar** y corrige lo que marque el panel de errores. La barra superior muestra cuántas filas son válidas y cuántas tienen errores. 5. Haz clic en **Publicar**. **Publicar reemplaza las checadas de esos días.** Antes de escribir, el diálogo de confirmación te dice cuántas checadas previas se borran, qué retardos se vuelven a calcular y qué incidencias se ven afectadas. Revísalo antes de confirmar. Si cierras la hoja sin publicar, la sesión se conserva como borrador. Al volver a **Captura masiva** puedes continuar donde te quedaste o descartarla y empezar de nuevo. ## Códigos QR [#códigos-qr] Los códigos QR permiten que el colaborador cheque desde la app. Se administran en **Configuración → Asistencia → Códigos QR**. 1. Haz clic en **Agregar código**. 2. Escribe el **Nombre** del punto de checado y, si quieres, elige la **Sucursal**. 3. Decide si la **Checada atada a ubicación** queda encendida. Encendida, la app debe reportar una ubicación dentro del alcance del código; si el colaborador está fuera o no comparte su ubicación, la checada no se acepta. Apagada, la checada se registra desde cualquier lugar y la ubicación se guarda si la app la reporta. 4. Elige el **Alcance de ubicación**: * **Rango**: captura el **Radio (metros)**, que viene en 100, y ubica el punto de checado en el mapa: busca la dirección, haz clic en el mapa o arrastra el marcador. * **Ciudad**: busca y elige uno o varios **Municipios**. Pueden ser de estados distintos, por ejemplo para cubrir una zona metropolitana. * **Estado**: elige uno o varios **Estados**. * **País**: la checada se acepta en cualquier lugar de México. Con ciudad o estado, el mapa dibuja los municipios o estados que elegiste. Sus límites son los del Marco Geoestadístico de INEGI. 5. Haz clic en **Crear código**. Si apagas la **Checada atada a ubicación**, el código conserva su alcance y lo recupera al volver a encenderla. **Tolerancia de frontera.** Los límites de los municipios y estados son aproximados, y el GPS del teléfono también tiene un margen de error. Para que un colaborador junto al límite no quede fuera, la checada se acepta hasta cierta distancia fuera del borde de su ciudad, su estado o el país. La distancia es de 100 metros y la cambias en **Configuración → Asistencia → Reloj checador**, en **Tolerancia de frontera (metros)**, de 0 a 1,000. No aplica a los códigos con alcance de rango, porque su radio ya es exacto. Desde el menú de opciones de cada código puedes **Ver código** y **Descargar PNG** para imprimirlo. Si un cartel se pierde o se copia, usa **Regenerar código**: el cartel anterior deja de funcionar y tienes que imprimir el nuevo. ### Checadas en vacaciones, permisos o incapacidad [#checadas-en-tiempo-libre] En **Configuración → Asistencia → Reloj checador**, sección **Checada por QR**, la opción **Checadas en vacaciones, permisos o incapacidad** decide qué pasa cuando un colaborador escanea un código en un día que tiene registradas vacaciones, un permiso o una incapacidad: * **Aceptar y señalar**, la opción predeterminada: la checada se registra, el día lleva la marca **Checada en vacaciones**, **Checada en permiso** o **Checada en incapacidad** y queda por revisar, como se explica en [Revisar las checadas](#revisar). * **Rechazar**: la checada no se registra y la app muestra "Vacaciones registradas para hoy. La checada no se registró. Contactar a Recursos Humanos si es un error.", "Permiso registrado para hoy. La checada no se registró. Contactar a Recursos Humanos si es un error." o "Incapacidad registrada para hoy. La checada no se registró. Contactar a Recursos Humanos si es un error." Aunque elijas **Rechazar**, la checada siempre se acepta en un día con medio día de tiempo libre, en un descanso o en un festivo, y cuando es la salida de un turno nocturno que empezó la noche anterior. La opción solo aplica a las checadas por QR. # Costo de la empresa (/docs/calculation/employer-cost) Además de lo que le paga al empleado, la empresa paga contribuciones que no salen del recibo del trabajador: las cuotas patronales del IMSS, la aportación al INFONAVIT y el impuesto sobre nómina. El recibo las muestra al final de la pestaña **Resumen**, en **Lo que le cuesta a la empresa**. Ninguna de ellas cambia el neto del empleado. ## Qué suma el costo patronal [#costo-patronal] La tabla **Costo patronal** tiene una línea por cada cargo de la empresa: * **IMSS patronal**: los ramos del IMSS a cargo del patrón. Los explica [Cuotas del IMSS](/docs/calculation/imss#ramos-del-patron). * **IMSS trabajador absorbido**: las cuotas obreras que la empresa cubre cuando corresponde conforme al artículo 36 de la LSS. * **INFONAVIT patronal**: la aportación de vivienda. * **Impuesto sobre nómina**: el impuesto estatal. * **Vales de despensa**: el importe de los vales, en tarjeta o en efectivo. * Los conceptos de tu organización de tipo obligación patronal. ## INFONAVIT [#infonavit] La aportación patronal al INFONAVIT es el 5% del SBC topado por los días que cotiza en el periodo. Las faltas y las ausencias la reducen; las incapacidades no, porque esos días siguen cotizando para el INFONAVIT. Con un SBC topado de $524.66 en una quincena sin incidencias: $524.66 × 5% × 15 = $393.50. Esta aportación es distinta del descuento por crédito INFONAVIT: la aportación la paga la empresa para el fondo de vivienda del trabajador; el crédito es una deuda del trabajador que se le descuenta en su recibo. Consulta [Deducciones y neto a depositar](/docs/calculation/net-pay#creditos). Los asimilados, los trabajadores por honorarios y los empleados que no aportan al IMSS no generan aportación al INFONAVIT. ## Impuesto sobre nómina [#isn] El impuesto sobre nómina es un impuesto de cada estado sobre lo que la empresa paga a sus trabajadores. Por eso depende de dónde trabaja el empleado: * **El estado** es la **Entidad del centro de trabajo** del empleado, en la sección **Registro** de su formulario. No es el estado de la razón social ni del registro patronal. * **La tasa** es la de ese estado. La mayoría cobra una sola tasa sobre la base. * **La base** son las percepciones que ese estado grava. Cada estado decide si incluye, por ejemplo, los vales o el fondo de ahorro. El sueldo entra ya sin los días que no se pagan. Una deducción propia con la opción **Reduce la base de ISN** se resta de esta base. Algunos estados no cobran una sola tasa: * **Sinaloa** cobra una tarifa progresiva sobre el total de la nómina mensual de la empresa en el estado. La plataforma suma la base de ISN de todos los empleados de ese estado en la corrida, para esa razón social, la lleva a un mes y con ese total obtiene una sola tasa efectiva. * **Campeche, Chiapas, Chihuahua, Durango y Guerrero** suman una sobretasa al impuesto, que se aplica también sobre una tasa propia de tu empresa. * **Querétaro** exenta un monto mensual de la nómina antes de cobrar su tasa, también si tu empresa captura una tasa propia. Todos los empleados de un estado en una misma corrida pagan la misma tasa efectiva, y los centavos que deja el redondeo se ajustan para que el ISN del estado sume exactamente el impuesto sobre su base total. En Sinaloa y Querétaro, las corridas extraordinarias, como aguinaldo o PTU, y los finiquitos utilizan la tasa que calculó la nómina ordinaria más reciente de la razón social en ese estado con la misma tarifa; si todavía no hay una, utilizan la tasa del primer tramo de la tarifa. Estos valores aplican a todos los periodos de 2026. La tasa y los conceptos de la base de cada estado se configuran en [Impuesto sobre nómina por estado](/docs/configuration/platform-catalogs#isn-por-estado). La línea **Impuesto sobre nómina** del recibo muestra, en su desglose, la **Base ISN del estado** y la tasa aplicada, que en Sinaloa es la tasa efectiva de la corrida. **Revisa la entidad del centro de trabajo de cada empleado.** Si no tiene una, su impuesto sobre nómina se calcula en cero. ## Costo total empresa [#costo-total-empresa] El **Costo total empresa** es lo que el empleado le cuesta a la empresa en el periodo: lo que se le paga en efectivo más el costo patronal. El subsidio para el empleo no cuenta, porque la empresa lo recupera al acreditarlo contra el ISR que retiene. Un ejemplo en una quincena sin incidencias, con un sueldo diario de $500.00, un SBC topado de $524.66, una prima de riesgo de trabajo de 0.5% y un estado con tasa de 3%, con los valores de 2026: | | Importe | | -------------------------------------- | ------------- | | Sueldo pagado | $7,500.00 | | IMSS patronal | $1,529.63 | | INFONAVIT patronal | $393.50 | | Impuesto sobre nómina ($7,500.00 × 3%) | $225.00 | | **Costo total empresa** | **$9,648.13** | La corrida muestra la suma de todos sus empleados en el panel **Lo que le cuesta a la empresa**. En Sinaloa la tasa depende del total de la nómina. Si el total mensual de la empresa en el estado es de $600,000.00, el impuesto es $12,000.00 + 2.6% del excedente de $500,000.00, que da $14,600.00, y la tasa efectiva es 2.4333%. Para el mismo empleado, el impuesto sobre nómina es $7,500.00 × 2.4333% = $182.50. # Exenciones y acumulados (/docs/calculation/exemptions-and-accumulators) Varias percepciones están exentas de ISR solo hasta un tope: la prima vacacional hasta 15 UMA al año, el aguinaldo hasta 30 UMA al año, los vales hasta el 40% de la UMA mensual cada mes. Cuando el tope vale por un mes o un año, un solo recibo no basta para saber cuánto queda: hay que recordar lo que ya se exentó en los periodos anteriores. Para eso la plataforma lleva un acumulado por empleado. Por eso la misma prima vacacional puede salir exenta completa en marzo y parcialmente gravada en julio: en julio ya se había usado parte del tope del año. ## Cómo funciona un acumulado [#acumulado] Cada tope tiene una ventana, el lapso en el que vale: * **Año o mes.** Lo exentado se guarda y pasa al siguiente periodo del mismo año o mes. El mes es el **Mes de acumulación** del periodo: en los calendarios semanales y catorcenales es por defecto el mes de la fecha de pago, y en las demás periodicidades el mes de la fecha de fin; los periodos creados antes conservan el mes en que terminan. Los vales de despensa siguen el mes del periodo (consulta [Cuándo se pagan los vales](/docs/configuration/contracts#frecuencia-de-vales)). Al empezar un año o un mes nuevo, el acumulado empieza en cero. * **Semana, periodo, día o evento.** El tope se calcula completo en cada periodo y no pasa al siguiente. En cada periodo, el cálculo toma el tope, le resta lo que ya se exentó en su ventana y exenta, como máximo, lo que queda. Lo que no cabe se grava. Algunos conceptos comparten un mismo tope. Las horas extra dobles, los días festivos trabajados y el descanso semanal trabajado comparten las 5 UMA por semana: lo que exenta uno ya no está disponible para los otros. ## Los topes de ley [#topes] Así vienen configurados en el catálogo inicial: | Percepción | Parte exenta | Tope | Ventana | | ----------------------------------------------------------------- | ----------------- | --------------------------------------- | ------- | | Horas extra dobles, días festivos y descanso semanal trabajados | 50% | 5 UMA por semana, compartidas | Periodo | | Prima vacacional | Todo lo que quepa | 15 UMA | Año | | Aguinaldo | Todo lo que quepa | 30 UMA | Año | | PTU pagada | Todo lo que quepa | 15 UMA | Año | | Prima dominical | Todo lo que quepa | 1 UMA por domingo trabajado | Evento | | Vales de despensa | Todo lo que quepa | 40% de la UMA mensual | Mes | | Prima de antigüedad, indemnización y gratificación por separación | Todo lo que quepa | 90 UMA por año de servicio, compartidas | Año | El valor de la UMA que se usa es el vigente a la fecha de fin del periodo. El valor oficial está en [Catálogos de plataforma](/docs/configuration/platform-catalogs). ## Horas extra [#horas-extra] El tope de las horas extra vale 5 UMA por cada semana del periodo: una semana en un calendario semanal, 15 ÷ 7 semanas en uno quincenal y 30.4 ÷ 7 en uno mensual. En una quincena, el tope es de unas 10.7 UMA. * **Horas dobles.** Se exenta el 50% del pago, sin pasar del tope. Si el empleado gana el salario mínimo, su pago de horas dobles está exento completo, sin tope. * **Horas triples.** Siempre gravan completas. Como el tope se comparte, el orden importa: primero se aplica a las horas extra, después a los días festivos trabajados y al final al descanso semanal trabajado. Un empleado con muchas horas extra puede agotar el tope y dejar gravado completo un día festivo trabajado en el mismo periodo. ## Un ejemplo a lo largo del año [#ejemplo] Con una UMA diaria de $100.00 para simplificar, el tope anual de la prima vacacional es de 15 × $100.00 = $1,500.00. Un empleado recibe $1,000.00 de prima vacacional en dos periodos del mismo año: | | Primer periodo | Segundo periodo | | ------------------------------- | -------------- | --------------- | | Prima vacacional pagada | $1,000.00 | $1,000.00 | | Exento aplicado antes en el año | $0.00 | $1,000.00 | | Tope que queda | $1,500.00 | $500.00 | | **Exento** | $1,000.00 | $500.00 | | **Gravado ISR** | $0.00 | $500.00 | Con los vales pasa lo mismo, pero el acumulado se reinicia cada mes. Con una UMA mensual de $3,040.00, el tope del mes es de $1,216.00. Si un empleado recibe $800.00 de vales en cada quincena de un mes, en la primera quincena se exentan los $800.00, y en la segunda solo quedan $416.00 exentos y $384.00 gravan. El mes siguiente vuelve a empezar con $1,216.00. ## Dónde verlo en el recibo [#en-el-recibo] La columna **Exento** de la sección **Percepciones**, en la pestaña **Cálculo paso a paso**, muestra lo que se exentó en este periodo. Para la prima vacacional y los vales, el desglose de la línea en la pestaña **Resumen** también muestra el tope y lo que ya se había usado: * **Prima vacacional**: **Tope anual exento** y **Exento aplicado en el año**. * **Vales de despensa**: **Tope exento**, con lo usado en el mes. ## Recalcular y quitar empleados [#recalcular] Recalcular un periodo no gasta el tope dos veces: antes de calcular, la plataforma descuenta lo que ese mismo periodo había exentado, y el resultado es el mismo que la primera vez. Si quitas a un empleado de una corrida, lo que esa corrida había exentado regresa a su acumulado y queda disponible para otros periodos. El acumulado suma lo exentado en todas las corridas calculadas del año o mes, sin importar el orden de los periodos. Si calculas un periodo posterior antes que uno anterior, el posterior usa el tope primero. # Cuotas del IMSS (/docs/calculation/imss) Las cuotas del IMSS financian los seguros del trabajador: enfermedad y maternidad, invalidez y vida, retiro, cesantía y vejez, riesgos de trabajo y guarderías. Cada seguro, o ramo, tiene su propio porcentaje, y una parte la paga el trabajador y otra el patrón. La parte del trabajador se descuenta en su recibo; la del patrón es un costo de la empresa. En el recibo, las cuotas del trabajador están en la sección **Cuotas IMSS del trabajador** de la pestaña **Cálculo paso a paso**, y se descuentan como una sola línea, **IMSS trabajador**. Las del patrón están en **Lo que le cuesta a la empresa**, en la pestaña **Resumen**. ## La base de todas las cuotas [#base] Todos los ramos se calculan igual: un porcentaje del **SBC topado** por los días que cotiza el ramo en el periodo. Cómo se obtiene el SBC lo explica [Salario base de cotización](/docs/calculation/sbc). Los días no son los mismos para todos los ramos. Una falta o una ausencia sin goce sigue cotizando para enfermedad y maternidad, pero no para los demás ramos. Una incapacidad, sea por enfermedad general, riesgo de trabajo o maternidad, solo sigue cotizando para retiro y para la aportación patronal al INFONAVIT. Las vacaciones y los permisos con goce cotizan en todos los ramos. La tabla completa está en [Los días cotizados](/docs/calculation/sbc#dias-cotizados). ## Los ramos del trabajador [#ramos-del-trabajador] * **Excedente 3 UMA**: solo sobre la parte del SBC que rebasa 3 UMA diarias. Si el SBC topado no llega a 3 UMA, esta cuota es cero. * **Prestaciones en dinero**. * **Gastos médicos pensionados**. * **Invalidez y vida**. * **Cesantía y vejez**. La sección suma los cinco en **Total IMSS trabajador**. Los porcentajes vigentes están en el catálogo **Cuotas IMSS obrero** de [Catálogos de plataforma](/docs/configuration/platform-catalogs). Un ejemplo con los valores de 2026, en una quincena sin incidencias, para un empleado con un SBC topado de $524.66. La base de la mayoría de los ramos es $524.66 × 15 = $7,869.90; la del excedente es ($524.66 − 3 UMA) × 15 = $2,590.95. | Ramo | Cuota | | -------------------------- | ----------- | | Excedente 3 UMA | $10.36 | | Prestaciones en dinero | $19.67 | | Gastos médicos pensionados | $29.51 | | Invalidez y vida | $49.19 | | Cesantía y vejez | $88.54 | | **Total IMSS trabajador** | **$197.27** | Con una falta en la quincena, invalidez y vida baja a $45.91 y cesantía y vejez a $82.63, porque cotizan 14 días. Excedente 3 UMA, prestaciones en dinero y gastos médicos pensionados no cambian: la falta sigue cotizando para enfermedad y maternidad. ## Los ramos del patrón [#ramos-del-patron] El patrón paga los mismos ramos con porcentajes mayores, y además: * **Cuota fija de enfermedad y maternidad**: un porcentaje de la UMA diaria por los días que cotiza enfermedad y maternidad, no del SBC. Es igual para todos los empleados, ganen lo que ganen. * **Riesgo de trabajo**: con la prima de riesgo del [registro patronal](/docs/configuration/employer-registrations) del empleado. * **Retiro**. * **Guarderías y prestaciones sociales**. La **Cesantía y vejez** del patrón no tiene un porcentaje fijo: sube según cuántas veces la UMA gana el empleado, medido con su SBC. La tabla vigente es el catálogo **Cesantía y vejez patronal**. Todos los ramos del patrón se suman en la línea **IMSS patronal** del costo de la empresa. Pasa el cursor sobre el ícono de información de la línea para ver cada ramo. Consulta [Costo de la empresa](/docs/calculation/employer-cost). ## Quién no paga cuotas [#sin-cuotas] * **Asimilados y trabajadores por honorarios.** No tienen salario base de cotización, así que ni ellos ni el patrón pagan cuotas del IMSS por ellos. * **Empleados que no aportan al IMSS.** Si en la sección **Registro** del empleado no está marcado **Aporta al IMSS**, el recibo no calcula cuotas del trabajador ni del patrón, aunque el empleado sea asalariado. # Cómo se calcula la nómina (/docs/calculation) Las guías de [Operar la nómina](/docs/payroll) explican qué hacer en cada pantalla. Esta sección explica el porqué de los números: de dónde sale cada importe del recibo, qué reglas lo limitan y por qué a veces no es el que esperabas. Cada página parte de lo que ves en la pestaña **Cálculo paso a paso** del recibo y usa ejemplos con cifras redondas. Los valores oficiales que cambian cada año, como la UMA, el salario mínimo o las tarifas de ISR, no se repiten aquí: están en [Catálogos de plataforma](/docs/configuration/platform-catalogs). ## El orden del cálculo [#orden] La plataforma calcula el recibo de cada empleado siempre en el mismo orden, porque cada paso usa el resultado del anterior: 1. **Las bases.** El sueldo diario, el factor de integración y el salario base de cotización (SBC). 2. **Los días.** Días del periodo, faltas, incapacidades y los días que se pagan y los que cotizan al IMSS. 3. **Las percepciones.** El sueldo del periodo y cada percepción, separada en su parte gravada y exenta. 4. **El ISR y el subsidio.** La base gravable, la tarifa, el subsidio para el empleo y el ISR a retener. 5. **Las cuotas del IMSS del trabajador.** 6. **Las deducciones.** ISR, IMSS, créditos y demás descuentos, en un orden que protege el neto. 7. **El costo de la empresa.** Las cuotas patronales, el INFONAVIT y el impuesto sobre nómina. ## Dónde se explica cada importe [#mapa-del-recibo] | Lo que ves en el recibo | Dónde aparece | Se explica en | | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | Sueldo diario, días del periodo, días pagados, faltas, faltas por acumulación de retardos, incapacidades, séptimo día proporcional, sueldo del periodo | Cálculo paso a paso: **Bases del cálculo** y **Determinación del sueldo del periodo** | [Sueldo y días del periodo](/docs/calculation/salary-and-days) | | Aguinaldo y prima vacacional integrados, parte variable, SDI/SBC sin topes y topado, SDI/SBC en UMA, tasa de cesantía y vejez patronal, días cotizados IMSS | Cálculo paso a paso: **Bases del cálculo** | [Salario base de cotización](/docs/calculation/sbc) | | Parte gravada y exenta de cada percepción | Resumen: **Percepciones**, el exento debajo del importe. Cálculo paso a paso: **Percepciones**, columnas **Gravado ISR** y **Exento** | [Bases de cada concepto](/docs/calculation/tax-bases) | | Una exención menor a la esperada, o que se agota a lo largo del año | Cálculo paso a paso: columna **Exento** de **Percepciones**. Resumen: el desglose de la prima vacacional y los vales | [Exenciones y acumulados](/docs/calculation/exemptions-and-accumulators) | | Base gravable, límite inferior, excedente, cuota fija, ISR causado, subsidio al empleo aplicado, ISR retenido | Cálculo paso a paso: **Determinación del ISR** | [ISR y subsidio para el empleo](/docs/calculation/isr-and-subsidy) | | Cuotas por ramo y **Total IMSS trabajador** | Cálculo paso a paso: **Cuotas IMSS del trabajador** | [Cuotas del IMSS](/docs/calculation/imss) | | Deducciones, percepciones no monetarias, columna **Acumulado**, **Neto a depositar** | Resumen: **Deducciones** y **Pago del periodo**. Cálculo paso a paso: **Determinación del neto a depositar** | [Deducciones y neto a depositar](/docs/calculation/net-pay) | | Cuotas patronales, IMSS trabajador absorbido, INFONAVIT, impuesto sobre nómina, **Costo total empresa** | Resumen: **Lo que le cuesta a la empresa** | [Costo de la empresa](/docs/calculation/employer-cost) | Si un importe no te cuadra, empieza por el ícono de información junto al concepto en el recibo: muestra la fórmula que se aplicó y los valores del empleado. La guía [Recibos y cálculo paso a paso](/docs/payroll/receipts#ver-desglose) explica cómo usarlo. # ISR y subsidio para el empleo (/docs/calculation/isr-and-subsidy) El ISR que se retiene en cada recibo sale de aplicar la tarifa del SAT a la base gravable del empleado. Si el empleado tiene derecho al subsidio para el empleo, el subsidio se resta de ese impuesto. En el recibo, todo el cálculo está en la sección **Determinación del ISR** de la pestaña **Cálculo paso a paso**. ## La base gravable [#base-gravable] La **Base gravable del periodo** es la suma de la parte gravada de las percepciones del periodo: la columna **Gravado ISR** de la sección **Percepciones**. Qué parte de cada percepción grava lo explican [Bases de cada concepto](/docs/calculation/tax-bases) y [Exenciones y acumulados](/docs/calculation/exemptions-and-accumulators). El sueldo entra a la base ya sin los días que no se pagan: una falta baja el sueldo y también el ISR. Una deducción propia con la opción **Reduce la base de ISR** se resta de la base antes de aplicar la tarifa, y la base nunca queda por debajo de cero. En **Cálculo paso a paso** aparece como **Deducciones que reducen la base**. Por ejemplo, con un sueldo quincenal de $4,740.00 y una deducción de $965.71 con esa opción, la base gravable del periodo es $3,774.29. ## Cómo se aplica la tarifa [#tarifa] La tarifa de ISR es una tabla de rangos. Cada rango tiene un límite inferior, una cuota fija y un porcentaje. El cálculo: 1. Busca el rango en el que cae la base. 2. Le resta el **Límite inferior del rango de la tarifa**; el resultado es el **Excedente del límite inferior**. 3. Multiplica el excedente por el porcentaje del rango: el **Impuesto marginal**. 4. Le suma la **Cuota fija del rango**. Las tarifas vigentes están en [Catálogos de plataforma](/docs/configuration/platform-catalogs). ## Tarifa del periodo o tarifa mensual [#que-tarifa] Cada calendario de nómina elige con qué tarifa se calcula su ISR, y la elección cambia cómo se ve el recibo: **Tarifa del periodo.** Un calendario quincenal usa la tarifa quincenal, uno semanal la semanal. Cada periodo se calcula solo con su propia base, sin mirar los anteriores. **Tarifa mensual.** Cada periodo suma su base a la de los periodos anteriores del mismo mes y la proyecta a un mes completo: la divide entre los días acumulados del mes y la multiplica por los días del mes, 30 o 30.4 según los **Días del mes** del calendario. A esa base mensual proyectada le aplica la tarifa mensual y toma solo la parte de los días ya pagados: el impuesto por los días acumulados entre los días del mes. De ahí resta lo que ya se retuvo en el mes. Cada periodo cuenta en su **Mes de acumulación**: en los calendarios semanales y catorcenales es por defecto el mes de la fecha de pago, y en las demás periodicidades el mes de la fecha de fin; los periodos creados antes conservan el mes en que terminan. En el recibo verás: * **Base gravable del periodo** * **Base gravable de periodos anteriores del mes** * **Base gravable acumulada del mes** * **Base mensual proyectada**, con los días acumulados del mes * **ISR del mes con la tarifa mensual** e **ISR a la fecha**, la parte de los días ya pagados * **ISR ya retenido en periodos anteriores del mes** y **Subsidio ya aplicado en periodos anteriores del mes**, cuando los hay. Los días acumulados son los días pagados de cada periodo: los días del periodo según el calendario, 15 en cada quincena, menos las faltas, los permisos sin goce y las incapacidades. Las vacaciones y los permisos con goce sí cuentan como pagados. Así, una falta baja la base y también los días, y la proyección sigue reflejando el sueldo diario del empleado. Si en el periodo no hubo ningún día pagado ni días previos en el mes, no se causa ISR ni subsidio; si hay otro ingreso gravable, se proyecta con los días del periodo para el ISR, sin subsidio. Con un ingreso parejo y sin ausencias, cada periodo del mes retiene lo mismo, y prácticamente lo mismo que con la tarifa del periodo. Si en un periodo del mes ya se retuvo de más, por ejemplo porque el ingreso bajó en la segunda quincena, el periodo no lleva deducción de ISR y la diferencia se devuelve en las percepciones como **Reintegro de ISR retenido en exceso**. La tarifa se elige al crear el calendario; consulta [Calendarios de nómina](/docs/configuration/calendars#tabla-de-isr). ## El subsidio para el empleo [#subsidio] El subsidio para el empleo es un crédito que reduce el ISR de los trabajadores de menores ingresos: * **Quién lo recibe.** Los trabajadores asalariados cuyo ingreso gravable no rebasa el tope del subsidio. Los asimilados y los trabajadores por honorarios no lo reciben, y tampoco se aplica en una corrida de aguinaldo. * **Cuánto es.** Con la tarifa mensual, el subsidio mensual entre los días del mes por los días pagados del periodo: una falta o una incapacidad reduce el subsidio de ese periodo. Con la tarifa del periodo, el subsidio completo del periodo; lo que sobra del subsidio se paga hasta la parte del subsidio del periodo que corresponde a los días pagados. En el mes nunca pasa del subsidio mensual. * **Su límite.** El subsidio que se descuenta del ISR nunca es mayor al ISR. Si el ISR es menor, el subsidio aplicado es igual al ISR, el ISR retenido queda en cero y lo que sobra del subsidio se le paga al empleado. Lo que se paga en un periodo nunca es mayor al subsidio de ese periodo, un periodo sin días pagados no paga subsidio, y lo que ya se pagó no se vuelve a descontar del ISR en los siguientes periodos del mes. Con la tarifa mensual, el tope se compara con la base mensual proyectada. Si en la primera quincena el empleado quedó debajo del tope y recibió subsidio, pero con la segunda quincena la proyección lo rebasa, el subsidio de la primera quincena, tanto el que se descontó del ISR como el que se le pagó, se recupera con la retención de la segunda. El monto del subsidio y su tope vigentes están en [Catálogos de plataforma](/docs/configuration/platform-catalogs). ## Cómo se ve en el recibo [#en-el-recibo] * En **Deducciones**, la línea **ISR** es el **ISR retenido**: el ISR causado menos el subsidio aplicado. Es el último renglón de la sección **Determinación del ISR**. * El subsidio que se descuenta del ISR no aparece como percepción: ya va descontado del ISR. * Si el subsidio es mayor que el ISR, la línea **ISR** no aparece, porque el ISR retenido es cero, y lo que sobra del subsidio aparece en **Percepciones** como **Subsidio para el empleo**, con los límites de la sección anterior: suma al neto a depositar. La sección **Determinación del ISR** cierra con ese importe. El CFDI timbrado lleva el ISR retenido como deducción y, en el otro pago de subsidio para el empleo, el subsidio causado y lo que se pagó al empleado. Las nóminas cerradas conservan el recibo con el que se cerraron. En algunas, la línea **ISR** es el ISR causado y el subsidio aparece en **Percepciones**; en otras, lo que sobra del subsidio aparece como **Subsidio para el empleo (informativo)** y no se pagó. Si recalculas una nómina que todavía no cierras, sus recibos toman la presentación de esta sección. ## Un ejemplo con tarifa mensual [#ejemplo] Un empleado asalariado con un sueldo diario de $350.00, en un calendario quincenal con tarifa mensual, sin incidencias, en un calendario con los días del mes configurados en 30.4. Con las tarifas y el subsidio de 2026: | | Primera quincena | Segunda quincena | | ----------------------------------------------------- | ----------------------------- | ----------------------------- | | Base gravable del periodo | $5,250.00 | $5,250.00 | | + Base gravable de periodos anteriores del mes | $0.00 | $5,250.00 | | = Base gravable acumulada del mes | $5,250.00 | $10,500.00 | | = Base mensual proyectada | $10,640.00 (÷ 15 días × 30.4) | $10,640.00 (÷ 30 días × 30.4) | | ISR del mes con la tarifa mensual | $798.65 | $798.65 | | = ISR a la fecha | $394.07 (× 15 ÷ 30.4 días) | $788.14 (× 30 ÷ 30.4 días) | | − ISR ya retenido en periodos anteriores del mes | | $129.77 | | − Subsidio ya aplicado en periodos anteriores del mes | | $264.30 | | = ISR causado | $394.07 | $394.07 | | − Subsidio al empleo aplicado | $264.30 | $264.30 | | = **ISR retenido** | **$129.77** | **$129.77** | El subsidio mensual es de $535.65, así que cada quincena recibe $264.30: $535.65 entre 30.4, por 15 días. En el mes se retienen $259.54. El mismo empleado en un calendario con tarifa quincenal retendría $129.81 en cada quincena. ## Aguinaldo, primas y PTU [#pagos-extraordinarios] La parte gravada del aguinaldo, la prima vacacional, la prima dominical y la PTU no se suma a la base del periodo. Si se sumara, un pago que se recibe una vez al año subiría el ingreso a un rango de la tarifa que no corresponde al del empleado. En su lugar, se calcula aparte con una tasa efectiva: la plataforma mensualiza el pago, calcula cuánto ISR agrega al sueldo mensual ordinario del empleado y aplica esa tasa al pago. En el recibo aparece como un renglón adicional en **Determinación del ISR**, con el importe gravado y la tasa aplicada. Los pagos por separación, como la indemnización, también se calculan aparte, con el procedimiento para pagos por separación. ## Asimilados y honorarios [#asimilados] Un asimilado a salarios o un trabajador por honorarios se calcula con la misma tarifa y la misma forma de acumular que un asalariado. La diferencia es que no recibe subsidio para el empleo, así que su recibo no muestra **ISR causado** ni **Subsidio al empleo aplicado**: el ISR de la tarifa es el ISR retenido. # Deducciones y neto a depositar (/docs/calculation/net-pay) El **Neto a depositar** es lo que recibe el empleado en su cuenta: el total de percepciones, menos lo que no se deposita, menos cada deducción. En el recibo, la sección **Determinación del neto a depositar** de la pestaña **Cálculo paso a paso** recorre esa resta renglón por renglón, y la columna **Acumulado** muestra el saldo después de cada movimiento. ## Las percepciones que no se depositan [#no-monetarias] Algunas percepciones forman parte del recibo y pagan impuestos, pero no se depositan, porque se entregan por otro medio, como una prestación en especie. Se restan primero, como **Percepciones no monetarias**, y lo que queda son las **Percepciones en efectivo**. Los vales de despensa en tarjeta son la excepción: aparecen como la percepción **Vales de despensa** y se descuentan completos en la deducción **Vales de despensa entregados**, que va antes que cualquier otra y nunca se recorta. Así el recibo y el CFDI muestran los vales como percepción y como deducción, y el neto no cambia. Los vales de despensa que el contrato entrega en **Efectivo** sí se depositan y no tienen esa deducción. En los conceptos propios, esto lo decide la opción **Se suma al neto a pagar** del concepto; consulta [Conceptos de nómina](/docs/configuration/concepts#crear). ## El orden de las deducciones [#orden] Las deducciones se aplican siempre en el mismo orden, del más importante al menos importante: 1. **Vales de despensa entregados**. 2. **Días no pagados**: faltas, retardos, incapacidades, ausencias sin goce y séptimo día proporcional. 3. **ISR**. 4. **IMSS trabajador**. 5. **Pensión alimenticia**. 6. **Crédito INFONAVIT**. 7. **Crédito FONACOT**. 8. **Descuentos de la empresa**: cuota sindical, fondo de ahorro del trabajador y de la empresa, caja de ahorro, préstamos, anticipos de sueldo, seguros, otros descuentos y los conceptos de deducción de tu organización. 9. **Ajustes retroactivos** que descuentan. El recibo muestra las deducciones en ese orden. Un concepto de deducción propio siempre va en el grupo de descuentos de la empresa, nunca antes de una deducción de ley. ## Qué pasa cuando no alcanza [#cuando-no-alcanza] Si las deducciones suman más que las percepciones en efectivo, la plataforma recorta de abajo hacia arriba: primero los descuentos de la empresa, después FONACOT, después INFONAVIT. Así se garantiza que: * **El neto nunca es negativo.** Si no alcanza, el neto a depositar es cero. * **Los vales entregados se descuentan completos.** **Vales de despensa entregados** nunca se recorta ni se omite, tampoco con salario mínimo, porque su propia percepción lo cubre. * **Las deducciones de ley se aplican completas.** Los días no pagados, el ISR, el IMSS del trabajador y la pensión alimenticia nunca se recortan. Si ni siquiera ellas caben en el periodo, el neto queda en cero y el recibo lo avisa. Lo que se recorta no desaparece sin aviso. En **Avisos del cálculo** verás cada caso: * Un préstamo, un anticipo o un seguro recortado sigue pendiente en el saldo del crédito: **"Préstamos": $161.35 pendiente por neto insuficiente; queda en el saldo.** * Otro descuento recortado no se aplica en este periodo: **Descuento "Cuota sindical" no aplicado por neto insuficiente ($50.00).** En un crédito FONACOT, lo que no se descontó sigue en su saldo y el crédito dura más. * Un ajuste retroactivo que no cupo pasa al siguiente periodo al cerrar. ## Los créditos del empleado [#creditos] Cada crédito activo del empleado es una línea de deducción. El ícono de información de la línea muestra cómo se calculó y, cuando el crédito tiene un total, el **Saldo al inicio del periodo** y el **Saldo tras el descuento**. Cómo se registran lo explica [Créditos y descuentos](/docs/employees/credits). **INFONAVIT.** El descuento mensual se reparte entre los días del bimestre, así que el mismo crédito puede descontar importes un poco distintos en quincenas de bimestres de 59 o de 62 días. Los días sin sueldo, como faltas e incapacidades, reducen el descuento en proporción. Si el descuento es un porcentaje, se calcula sobre el SBC sin el tope de 25 UMA. Una vez por bimestre se cobra además el **Seguro de vivienda INFONAVIT**. El crédito se descuenta hasta que registras su suspensión. **FONACOT.** El descuento mensual de la cédula se reparte entre los periodos del mes. Si en un periodo el empleado no tuvo ningún día con sueldo, no se descuenta nada y el crédito se recorre. El último descuento nunca rebasa el saldo pendiente, y al liquidarse el crédito deja de descontarse. **Pensión alimenticia.** Se calcula con la base que indica el oficio del juzgado, por ejemplo un porcentaje de las percepciones o del neto después de ISR e IMSS. Varias pensiones se calculan cada una sobre la misma base, sin descontarse entre ellas. La línea muestra el nombre del beneficiario. **Préstamos y anticipos.** Descuentan un importe fijo por periodo hasta liquidar el saldo. ## Un ejemplo [#ejemplo] Una quincena con un sueldo diario de $500.00 y una falta. El ISR y el IMSS son ilustrativos: | Concepto | Importe | Acumulado | | -------------------------- | --------- | ------------- | | Total de percepciones | | $7,500.00 | | − Faltas | $500.00 | $7,000.00 | | − Séptimo día proporcional | $83.33 | $6,916.67 | | − ISR | $700.00 | $6,216.67 | | − IMSS trabajador | $180.00 | $6,036.67 | | − Crédito FONACOT | $400.00 | $5,636.67 | | − Préstamos | $1,500.00 | $4,136.67 | | **= Neto a depositar** | | **$4,136.67** | Si el préstamo fuera de $6,000.00, solo cabrían $5,636.67: se descontaría eso, el neto quedaría en cero, y los $363.33 restantes seguirían en el saldo del préstamo con un aviso en el recibo. ## Cuando el recibo no se puede timbrar [#no-se-puede-timbrar] El aviso **Este recibo no se puede timbrar** aparece cuando alguna percepción o deducción con importe no tiene clave SAT, o tiene una clave que no existe en el catálogo del SAT. Cada renglón del aviso nombra el concepto y la razón. Asigna la clave en el [catálogo de conceptos](/docs/configuration/concepts#codigo-y-clave-sat) y recalcula la nómina antes de cerrarla. # Sueldo y días del periodo (/docs/calculation/salary-and-days) El sueldo de un periodo parte de dos datos: el sueldo diario del empleado y los días que paga el periodo. A partir de ahí se descuentan los días que no se pagan. En el recibo, este cálculo está en la sección **Determinación del sueldo del periodo** de la pestaña **Cálculo paso a paso**. ## El sueldo diario [#sueldo-diario] La plataforma guarda el sueldo de cada empleado como un importe diario. Al darlo de alta puedes capturarlo por día, semana, quincena, mes o año; la plataforma lo divide entre los días de ese periodo y guarda el resultado. Con los valores de la plataforma, una semana son 7 días, una quincena 15 y un año 365. Los días del mes son 30 o 30.4 y los elige cada calendario; al capturar el sueldo se utilizan los del **Calendario de nómina** que seleccionaste. Por ejemplo, un sueldo mensual de $20,000.00 se guarda como $20,000.00 ÷ 30 = $666.67 diarios; con un mes de 30.4 días, como $657.89. Los días de la semana, la quincena y el año están en el catálogo **Factor de días por semana/quincena/mes/año** de [Catálogos de plataforma](/docs/configuration/platform-catalogs); los días del mes se configuran en el [calendario](/docs/configuration/calendars#dias-del-mes). La corrida toma el sueldo diario del empleado al abrir el periodo. Si el sueldo cambia después, el nuevo entra cuando aplicas los cambios a la corrida. ## Los días del periodo [#dias-del-periodo] Cada periodo paga un número fijo de días según la frecuencia del calendario, sin importar cuántos días tenga en el calendario: | Frecuencia | Días del periodo | | ---------- | ------------------------------------------------ | | Semanal | 7 | | Decenal | 10 | | Catorcenal | 14 | | Quincenal | 15 | | Mensual | 30 o 30.4, según los días del mes del calendario | Por eso una quincena de febrero, de 13 o 14 días naturales, y una quincena de 16 días pagan lo mismo: 15 días de sueldo. **Si el empleado causa baja dentro del periodo**, los días del periodo se cuentan desde el inicio del periodo hasta su último día laborado, incluido. Una baja el día 13 en un periodo del 1 al 15 paga 13 días. ## Los días que no se pagan [#dias-que-no-se-pagan] **Faltas injustificadas.** Cada falta descuenta un día de sueldo. Las faltas son días completos. **Faltas por acumulación de retardos.** Cada 3 retardos del periodo cuentan como una falta. Los retardos que sobran no pasan al siguiente periodo: con 5 retardos se descuenta una falta y los otros 2 no generan descuento. La regla es fija y no se configura. **Ausencias sin goce de sueldo.** Un permiso sin goce se descuenta igual que una falta, aunque esté autorizado. **Faltas justificadas.** Una falta justificada se descuenta igual que un permiso sin goce, pero en su propia línea y sin contar como falta injustificada. **Incapacidades.** Los días de incapacidad no se pagan como sueldo, sea por enfermedad general, riesgo de trabajo o maternidad. El trabajador recibe del IMSS el subsidio por incapacidad que le corresponda; ese pago no pasa por la nómina. **Séptimo día proporcional.** El día de descanso semanal se gana con los días trabajados. Por cada día sin sueldo (faltas, faltas por retardos, faltas justificadas y ausencias sin goce) se pierde un sexto de día. Las incapacidades no descuentan séptimo día. Este descuento solo se aplica si el calendario del empleado tiene marcada la casilla **Aplicar descuento de séptimo día proporcional**, como viene al crearlo. Si está desmarcada, el día de descanso se paga completo y la deducción no aparece; consulta [El séptimo día proporcional](/docs/configuration/calendars#septimo-dia). Las vacaciones y los permisos con goce no se descuentan: se pagan como días normales. ## El sueldo del periodo [#sueldo-del-periodo] Los **Días pagados** son los días del periodo menos los días que no se pagan. El **Sueldo del periodo** es el sueldo diario por los días pagados. Un ejemplo en una quincena, con un sueldo diario de $400.00, 1 falta y 4 retardos: | Días | | | ------------------------------------ | ------- | | Días del periodo | 15 | | − Faltas injustificadas | 1 | | − Faltas por acumulación de retardos | 1 | | − Séptimo día proporcional (2 ÷ 6) | 0.3333 | | = Días pagados | 12.6667 | El sueldo del periodo es $400.00 × 12.6667 = $5,066.67. ## Cómo se ve en el recibo [#en-el-recibo] En la pestaña **Resumen**, el **Sueldo** aparece en **Percepciones** por el periodo completo: $400.00 × 15 = $6,000.00 en el ejemplo. Cada día que no se paga aparece como su propia línea en **Deducciones**: **Faltas**, **Retardos (faltas acumuladas)**, **Ausencia sin goce de sueldo**, **Falta justificada**, una línea por cada tipo de incapacidad y **Séptimo día proporcional**. Así puedes ver cuánto descontó cada motivo. En el ejemplo, las deducciones por días suman $400.00 + $400.00 + $133.33 = $933.33, y $6,000.00 − $933.33 = $5,066.67, el mismo sueldo del periodo. El ISR se calcula sobre el sueldo del periodo, no sobre el sueldo completo: los días que no se pagan tampoco pagan impuesto. Consulta [ISR y subsidio para el empleo](/docs/calculation/isr-and-subsidy). # Salario base de cotización (/docs/calculation/sbc) El salario base de cotización (SBC), también llamado salario diario integrado (SDI), es el salario diario sobre el que se pagan las cuotas del IMSS y la aportación al INFONAVIT. No es el sueldo que recibe el empleado: es su sueldo diario más la parte diaria de las prestaciones que cobra a lo largo del año, como el aguinaldo y la prima vacacional. Por eso siempre es mayor que el sueldo diario. En el recibo, el SBC se arma pieza por pieza en la sección **Bases del cálculo** de la pestaña **Cálculo paso a paso**: el **Sueldo diario**, lo que suman el **Aguinaldo** y la **Prima vacacional**, la **Parte variable**, el **SDI/SBC sin topes**, el **SDI/SBC topado** y los **Días cotizados IMSS**. Los recibos calculados antes de este desglose muestran un resumen con el factor de integración, el SBC diario y el SBC topado. ## El factor de integración [#factor-de-integracion] El aguinaldo y la prima vacacional se pagan una o dos veces al año, pero forman parte del salario del empleado. Para repartirlos en cada día del año, la plataforma calcula un factor de integración: **Factor de integración** = 1 + días de aguinaldo ÷ 365 + (días de vacaciones × prima vacacional) ÷ 365 Los días de aguinaldo, los días de vacaciones y el porcentaje de prima vacacional salen del contrato del empleado, como describe [Factor de integración (SBC)](/docs/configuration/contracts#factor-sbc). Los días de aguinaldo y los de vacaciones son los que le corresponden en su año de antigüedad actual, así que el factor sube con los años: un contrato más generoso o un empleado con más antigüedad dan un factor mayor. Si el contrato tiene **No se calcula** en **Pago de prima vacacional**, la prima vacacional no entra al factor: queda 1 + días de aguinaldo ÷ 365. Con las prestaciones mínimas de ley en el primer año (15 días de aguinaldo, 12 días de vacaciones y 25% de prima vacacional): 1 + 15 ÷ 365 + (12 × 0.25) ÷ 365 = 1 + 0.041096 + 0.008219 = **1.049315** En el segundo año, con 14 días de vacaciones, el factor pasa a 1.0507. En la nómina, el año de antigüedad se mide al primer día del periodo. Si el aniversario cae dentro de una quincena, el factor nuevo aplica desde el periodo que inicia en o después del aniversario. ## El SBC diario [#sbc-diario] El SBC diario es el sueldo diario multiplicado por el factor de integración, más la parte variable del salario si la hay. Con un sueldo diario de $500.00, el factor de 1.049315 del ejemplo y sin parte variable: $500.00 × 1.049315 = **$524.66** En el recibo se ve como una suma: **Sueldo diario** $500.00, **+ Aguinaldo** $20.55 y **+ Prima vacacional** $4.11 dan el **SDI/SBC sin topes** de $524.66. El recibo muestra el factor con cuatro decimales, pero el cálculo usa el factor completo. Por eso, si multiplicas $500.00 por 1.0493 obtienes $524.65, un centavo menos que el SBC del recibo. Puedes ver el SBC de un empleado antes de calcular la nómina: al editarlo, la sección **SDI/SBC** muestra el valor sin topes, el topado, el factor y el **Desglose del cálculo**. ## La parte variable [#parte-variable] Los pagos que cambian de un periodo a otro, como comisiones, bonos o propinas, integran al SBC con un promedio por bimestre (enero-febrero, marzo-abril, etc.) que aplica desde el primer día del bimestre siguiente. **Qué conceptos cuentan.** Cada percepción tiene una integración al SDI: **Fija**, **Variable**, **Mixta** o **No integra**. Solo las variables y mixtas entran al promedio, con la parte que integra su base **SDI (LFT)** en el [tratamiento fiscal](/docs/configuration/concepts#tratamiento-fiscal). Un concepto propio nace como **Fija**; la cambias en su [formulario](/docs/configuration/concepts#crear). **Cómo se calcula.** Lo que integra el pago variable de las nóminas cerradas del bimestre se divide entre los **Días cotizados IMSS** de sus recibos ordinarios, que ya descuentan faltas, ausencias e incapacidades por enfermedad general; sin esos recibos, entre los días activos del bimestre. Por ejemplo, $1,180.00 de comisiones en enero-febrero con 59 días cotizados suman $20.00 diarios al SBC desde el 1 de marzo. **Propuestas.** El promedio nunca cambia el SBC por sí solo: cuando recalculas el bimestre se deja una propuesta por cada empleado cuyo SBC cambiaría, con el desglose por concepto. Si cierras tarde una nómina del bimestre, recalcula y la propuesta se actualiza. Mientras está pendiente puedes **Ajustar** importes o días con un motivo; **Aprobar** aplica el SBC desde el primer día del bimestre siguiente y agrega la modificación a [Movimientos IMSS](/docs/employees/imss-movements); **Rechazar** pide un motivo y conserva el promedio anterior, igual que una propuesta pendiente. **Dónde se opera.** En **Nómina → SBC bimestral**, elige en **Filtros → Bimestre** el bimestre en que aplica el nuevo SBC y haz clic en **Recalcular bimestre**. Las pestañas **Pendientes**, **Aprobadas** y **Rechazadas** muestran el **SBC actual**, el **SBC propuesto**, la **Diferencia**, el **Pago variable**, los **Días** y el **Promedio variable** de cada empleado; puedes filtrar por razón social y buscar por nombre o número. En el menú de cada fila están **Ver detalle**, con el desglose por concepto y quién ajustó o decidió y por qué, y, en las pendientes, **Aprobar**, **Ajustar** y **Rechazar**. Al ajustar ves el SBC nuevo antes de guardar. Si la propuesta cambió desde que la revisaste, la plataforma no guarda tu decisión ni tu ajuste y te pide revisarla de nuevo. Si ya presentaste al IMSS el aviso de una propuesta aprobada y un recálculo cambia sus cifras, la propuesta se queda aprobada y muestra **Cifras cambiaron** con el SBC que darían las cifras actuales; revisa el SBC del empleado y presenta la corrección en el IMSS. Para consultar las propuestas necesitas permiso para ver la nómina y los sueldos; para recalcular, ajustar, aprobar o rechazar, permiso para ejecutar la nómina en la razón social del empleado. También puedes operarlo desde los [Conectores de IA](/docs/ai/mcp). ## El piso y el tope [#piso-y-tope] La ley fija un mínimo y un máximo para el SBC. El **SDI/SBC topado** es el SBC diario después de aplicarlos, y su nota en el recibo dice cuál de los dos límites se aplicó: * **Piso**: el SBC no puede ser menor al salario mínimo diario. Se usa el salario mínimo de la zona económica del empleado, general o de la Zona Libre de la Frontera Norte. * **Tope**: el SBC no puede ser mayor a 25 veces la UMA diaria. Un empleado que gana más que eso cotiza sobre el tope, sin importar cuánto gane. La mayoría de los empleados queda entre los dos límites, y entonces el SBC topado es igual al SBC sin topes. Los valores vigentes de la UMA, del salario mínimo y del tope están en [Catálogos de plataforma](/docs/configuration/platform-catalogs). ## Los días cotizados [#dias-cotizados] Las cuotas del IMSS y el INFONAVIT se calculan sobre el SBC topado multiplicado por los días que cotiza cada ramo. Todos parten de los días naturales del periodo en que el empleado estuvo contratado, aunque el sueldo se pague sobre 15 días por quincena o 30 o 30.4 por mes: una segunda quincena de un mes de 31 días cotiza 16 días, la segunda de febrero 13 o 14, y un periodo mensual de 28 a 31. De ahí, cada ramo descuenta distintos días: | Ramos | Faltas y ausencias | Incapacidades | | --------------------------------------------------------------------------------------------------------- | ------------------ | ---------------- | | Enfermedad y maternidad: cuota fija, excedente 3 UMA, prestaciones en dinero y gastos médicos pensionados | Siguen cotizando | Se descuentan | | Invalidez y vida, riesgo de trabajo, guarderías y cesantía y vejez | Se descuentan | Se descuentan | | Retiro e INFONAVIT patronal | Se descuentan | Siguen cotizando | Cuentan como faltas y ausencias las faltas, incluidas las que resultan de acumular retardos, las ausencias sin goce de sueldo, como un permiso sin goce, y las faltas justificadas. Las incapacidades son las tres: por enfermedad general, por riesgo de trabajo y por maternidad. Las vacaciones y los permisos con goce no se descuentan en ningún ramo, porque se pagan como días normales. Los **Días cotizados IMSS** del recibo son los días del periodo menos las faltas, las ausencias y las incapacidades por enfermedad general. Son los que dividen el pago variable del bimestre. Cuando los ramos cotizan días distintos, debajo aparecen **Días cotizados enf. y mat.**, **Días cotizados IV, RT, guarderías y CV** y **Días cotizados retiro e INFONAVIT**. Siguiendo el ejemplo, en una quincena de 15 días con una falta, enfermedad y maternidad cotiza 15 días, con una base de $524.66 × 15 = $7,869.90, e invalidez y vida y cesantía y vejez cotizan 14, con una base de $524.66 × 14 = $7,345.24. Cómo se reparten esas bases entre los ramos del IMSS lo explica [Cuotas del IMSS](/docs/calculation/imss). ## Para qué más sirve el SBC [#otros-usos] * **Cesantía y vejez a cargo del patrón.** Su porcentaje depende de cuántas veces la UMA gana el empleado, medido con su SBC. El recibo muestra ese múltiplo en **SDI/SBC en UMA** y el porcentaje en **Tasa de cesantía y vejez patronal**. * **Finiquitos e indemnizaciones.** Usan el **SDI/SBC sin topes**: el mismo salario integrado, sin el tope de 25 UMA. Consulta [Finiquito y liquidación](/docs/terminations). ## Quién no tiene SBC [#sin-sbc] Solo los trabajadores asalariados tienen salario base de cotización. Un asimilado a salarios o un trabajador por honorarios no cotiza al IMSS, así que su recibo no muestra SBC ni días cotizados, y no se le calculan cuotas del IMSS ni INFONAVIT. ## Cuándo cambia el SBC [#cambios] La corrida toma el sueldo, el contrato y el promedio variable del empleado al abrir el periodo; el promedio es el que está vigente el primer día del periodo, así que un periodo que cruza el cambio de bimestre conserva el anterior. Si después cambian, o apruebas una propuesta, el SBC nuevo entra cuando aplicas los cambios a la corrida; la guía [Abrir y calcular un periodo](/docs/payroll/run#cambios-en-los-datos-de-los-empleados) explica cómo. ## Corregir las variables del SBC [#corregir] El SBC nunca se captura directamente: se calcula con sus variables. Si una variable no es la correcta, por ejemplo porque el empleado tiene comisiones o bonos que se pagaron fuera de la plataforma, se corrige la variable y el SBC se recalcula. En la sección **SDI/SBC** del formulario del empleado, marca **Modificar variables del SDI/SBC** y captura lo que cambia: el sueldo diario a integrar, los días de aguinaldo o de vacaciones, la prima vacacional, un factor de integración, o los importes diarios variables, como el **Promedio variable del bimestre**, las comisiones y bonos, el tiempo extra o los premios de asistencia y puntualidad. Lo que dejes vacío conserva el valor real del empleado. Antes de guardar ves el SBC actual junto al que resulta con los cambios; escribe el **Motivo** y haz clic en **Guardar variables**. Necesitas permiso para administrar la configuración de nómina. La corrección no cambia el sueldo que se le paga al empleado ni su antigüedad: solo cambia su SBC. En el recibo, los importes variables aparecen en la **Parte variable**, y un factor capturado a mano aparece como **Factor capturado** en lugar del aguinaldo y la prima vacacional. Un importe variable capturado aquí manda sobre el promedio bimestral del pago variable hasta que lo elimines. Como cualquier cambio en los datos del empleado, una corrección llega a una corrida ya calculada cuando aplicas los cambios. Los periodos cerrados conservan el SBC con el que se calcularon. # Bases de cada concepto (/docs/calculation/tax-bases) Un mismo pago puede estar exento de ISR y aun así causar impuesto sobre nómina, o pagar ISR completo y no integrar al salario base de cotización. Cada contribución la define una ley distinta, con su propia lista de lo que cuenta y lo que no. Por eso la plataforma no le da a cada concepto un solo "gravado" o "exento", sino una respuesta por cada base: la matriz de concepto por base. La matriz se consulta y se ajusta en **Tratamiento fiscal**, desde el menú de cada concepto; la guía [Conceptos de nómina](/docs/configuration/concepts#tratamiento-fiscal) explica la pantalla. Esta página explica qué hace el cálculo con ella. ## Las seis bases [#bases] | Base | La pregunta que responde | Dónde se nota | | ------------------- | -------------------------------------------------- | ------------------------------------------------------------------- | | **ISR** | ¿Paga impuesto sobre la renta? | Columnas **Gravado ISR** y **Exento** del recibo, y el ISR retenido | | **SBC · IMSS** | ¿Integra al salario base de cotización? | Cuotas del IMSS | | **SBC · INFONAVIT** | ¿Integra para la aportación de vivienda? | Aportación al INFONAVIT | | **SDI (LFT)** | ¿Cuenta para finiquitos e indemnizaciones? | Finiquito y liquidación | | **ISN** | ¿Causa impuesto sobre nómina? | Impuesto sobre nómina, en el costo de la empresa | | **PTU** | ¿Forma parte de la base del reparto de utilidades? | El reparto de utilidades, fuera del recibo | ## Los tipos de tratamiento [#tratamientos] En cada base, un concepto puede: * **Entrar completo**: **Gravado**, **Integra** o **Causa**. * **No entrar**: **Exento**, **No integra** o **No causa**. * **Entrar en un porcentaje**: solo esa parte entra a la base y el resto queda fuera. * **Estar exento hasta un tope**: la parte que cabe en el tope queda exenta y el excedente entra a la base. El tope se mide en UMA, salario mínimo o un porcentaje del sueldo o del SBC, y puede valer por día, periodo, semana, mes, año o evento. Algunos conceptos exentan solo una parte de lo pagado y además la limitan con el tope, como las horas extra: exentas al 50%, sin pasar de 5 UMA por semana. Cómo se lleva la cuenta de un tope a lo largo de varios periodos lo explica [Exenciones y acumulados](/docs/calculation/exemptions-and-accumulators). ## Ejemplos de conceptos de ley [#ejemplos] Así vienen configurados algunos conceptos en el catálogo inicial de la organización: | Concepto | ISR | SBC · IMSS | ISN | | ----------------------------------- | ----------------------------------------- | ------------------------------ | ---------------- | | Sueldo | Gravado | Integra | Causa | | Horas extra dobles | Exento al 50%, hasta 5 UMA por semana | No integra | Causa | | Horas extra triples | Gravado | Integra | Causa | | Prima vacacional | Exento hasta 15 UMA al año | Va en el factor de integración | Causa | | Aguinaldo | Exento hasta 30 UMA al año | Va en el factor de integración | Causa | | Prima dominical | Exento hasta 1 UMA por domingo trabajado | Integra | Causa | | Vales de despensa | Exento hasta 40% de la UMA mensual al mes | Exento hasta un tope | Causa | | Premios de asistencia y puntualidad | Gravado | Exento hasta 10% del SBC | Causa | | PTU pagada | Exento hasta 15 UMA al año | No integra | Varía por estado | Los premios de asistencia son el caso contrario al aguinaldo: pagan ISR completo, pero en parte no integran al salario base de cotización. ## Qué base usa hoy el cálculo del recibo [#uso-en-el-recibo] **ISR.** Es la base que decide las columnas **Gravado ISR** y **Exento** de la sección **Percepciones** del recibo. La parte gravada de cada percepción forma la base del impuesto; la exenta no paga ISR. Cuando el recibo habla de gravado y exento, siempre se refiere al ISR. **ISN.** Decide qué percepciones suman a la base del impuesto sobre nómina. Como cada estado define su propia base, la plataforma usa el tratamiento del estado del centro de trabajo del empleado cuando lo hay. Consulta [Costo de la empresa](/docs/calculation/employer-cost). **SBC · IMSS, SBC · INFONAVIT y SDI.** Documentan cómo integra cada concepto según la ley. El salario base de cotización del periodo se calcula con el sueldo diario, el factor de integración de las prestaciones y la parte variable, como explica [Salario base de cotización](/docs/calculation/sbc); marcar un concepto propio como **Integra** no cambia por sí solo las cuotas del IMSS del periodo. Los pagos de un concepto con integración al SDI variable o mixta sí entran al SBC: la parte que integra su base **SDI (LFT)** forma un promedio bimestral que, una vez aprobado, aplica desde el bimestre siguiente, como explica [La parte variable](/docs/calculation/sbc#parte-variable). **PTU.** Define la base del reparto de utilidades y no aparece en el recibo de nómina ordinaria. Los **otros pagos**, como el subsidio para el empleo, no son gravados ni exentos: se reportan al SAT sin afectar ninguna base. ## Cuándo aplica un cambio [#cuando-aplica] Cada cálculo de una corrida abierta toma la matriz de conceptos como está configurada en ese momento: un cambio en el tratamiento fiscal de un concepto aplica en cuanto recalculas. Una corrida cerrada conserva la matriz con la que se cerró. Los valores oficiales también se toman al calcular: si un concepto está exento hasta 15 UMA, el tope sale de la matriz vigente y el valor de la UMA es el vigente a la fecha de fin del periodo. # CFDI de nómina (/docs/cfdi) El CFDI de nómina es el comprobante fiscal que respalda cada recibo ante el SAT. Cerrar una nómina no la timbra: el timbrado es un paso aparte, que haces cuando ya confirmaste que todo está pagado. Después das seguimiento a cada recibo en la bandeja de timbrado y reintentas los que fallan. El recibo en PDF que descargas de una corrida es informativo: no sustituye el CFDI de nómina timbrado. Para que tus recibos se puedan timbrar, deja listos los datos del emisor, de los conceptos y de los empleados que se describen a continuación. ## Datos del emisor [#datos-del-emisor] Cada razón social es el emisor de los recibos de sus empleados. Para timbrar necesita su identidad fiscal completa: * **RFC**. * **Razón social fiscal**, tal como aparece en la constancia de situación fiscal. * **Régimen fiscal**, por ejemplo 601, General de Ley Personas Morales. * **Código postal** del domicilio fiscal. * **Certificado de sello digital (CSD)** vigente y cargado al PAC. Los cuatro primeros datos se capturan en el formulario de la razón social, en **Configuración → Organización → Razón social**. La [guía de razones sociales](/docs/configuration/legal-entities) explica ese formulario, y [Primeros pasos](/docs/getting-started#identidad-fiscal) resume la identidad fiscal. La columna **Identidad fiscal** de la tabla de razones sociales indica **Completa** o **Incompleta**; pasa el cursor sobre ella para ver qué falta. Si falta algo, la página del calendario en **Nómina → Cálculo** también muestra el aviso **Identidad fiscal incompleta** con los datos pendientes. El aviso no impide calcular ni cerrar la nómina. ## Certificado de sello digital [#certificado-de-sello-digital] El CSD es el certificado que el SAT emite a la empresa para sellar sus comprobantes. Tramítalo en el SAT y súbelo a la plataforma: 1. En la tabla de razones sociales, abre el menú de opciones de la fila y elige **Certificado de sello digital**. 2. Selecciona el **Certificado (.cer)** y la **Llave privada (.key)**. 3. Escribe la **Contraseña de la llave privada**. 4. Haz clic en **Subir CSD**. ![Diálogo para subir el certificado de sello digital](/images/guides/configuration/legal-entity-csd-dialog.webp) La llave privada y la contraseña se guardan cifradas y nunca se vuelven a mostrar. Antes de guardarlos, la plataforma revisa que: * El RFC de la razón social ya esté capturado. * El archivo .cer sea un certificado válido del SAT y su RFC coincida con el de la razón social. * El certificado esté vigente. * La contraseña abra la llave privada y la llave corresponda al certificado. Si algo no cuadra, un mensaje te dice qué revisar y el CSD no se guarda. ### Estatus del CSD [#estatus-del-csd] Al guardarlo, la plataforma carga el CSD al PAC, el proveedor que timbrará los recibos. Al volver a abrir **Certificado de sello digital** verás el RFC del certificado, su número, su vigencia y el **Estatus en el PAC**: * **Cargando al PAC**: la carga está en proceso. Espera a que termine. * **Cargado al PAC**: el CSD está listo. * **Error al cargar al PAC**: la carga no se completó. El diálogo muestra el motivo; haz clic en **Reintentar carga**. ![Detalle del CSD cargado al PAC con su vigencia](/images/guides/configuration/legal-entity-csd-status.webp) Cuando faltan 30 días o menos para que venza, el diálogo muestra el aviso **El CSD vence pronto**, y **El CSD está vencido** cuando ya venció. Sin un CSD vigente no se podrán timbrar recibos. ### Renovar o eliminar el CSD [#renovar-o-eliminar-el-csd] * Para reemplazarlo por uno nuevo, haz clic en **Renovar CSD**, selecciona los archivos nuevos con su contraseña y confirma con **Renovar CSD**. * Para quitarlo, haz clic en **Eliminar CSD** y confirma en el diálogo **Eliminar CSD**. Ninguna de las dos acciones está disponible mientras el estatus sea **Cargando al PAC**. ## Conceptos con clave SAT [#conceptos-con-clave-sat] Cada percepción y deducción que aparece en un recibo necesita su clave del catálogo del SAT. La plataforma te avisa cuando falta alguna: * En **Configuración → Organización → Razón social**, la columna **Conceptos timbrables** dice **Completos** o señala los conceptos sin clave. * En **Nómina → Cálculo**, al abrir un calendario, aparece el aviso **Conceptos sin clave SAT** con los conceptos que impedirían timbrar. * En el recibo de un empleado aparece el aviso **Este recibo no se puede timbrar** con lo que falta. La clave se asigna en **Configuración → Incidencias → Conceptos**. La guía [Conceptos de nómina](/docs/configuration/concepts) explica cómo. ## Datos del empleado [#datos-del-empleado] Cada recibo timbrado lleva también los datos fiscales del empleado. Revisa que cada empleado los tenga completos en su formulario: * **RFC** y **CURP**, en la sección **Registro**. * **Código postal**, en la sección **Alta a nómina**. Es el de su domicilio, o el **Código postal fiscal** si está marcada **Domicilio fiscal distinto al domicilio**. * **Régimen fiscal (SAT)**, que para un asalariado es 02, Sueldos y salarios, y **Tipo de jornada**, en la sección **Alta a nómina**. Son los mismos datos que la plataforma revisa para activar a un empleado en nómina, así que un empleado **En nómina** ya los tiene. La guía [Primeros pasos](/docs/getting-started#primer-empleado) explica cómo capturarlos. ## Timbrar una nómina [#timbrar-una-nomina] Solo se timbra una corrida cerrada. Un finiquito, además, debe estar marcado como pagado en su escenario. 1. En la corrida cerrada, haz clic en **Timbrar** en la tarjeta **Timbrado**. También puedes hacerlo desde **Nómina → Timbrado**: haz clic en **Timbrar nómina** y elige la corrida. 2. Revisa la **Fecha de pago**. Por omisión es la fecha de pago del periodo, o la fecha en que se pagó el finiquito. Es la fecha que lleva cada recibo ante el SAT. En un finiquito no puede ser anterior a la fecha de baja ni posterior a hoy. 3. Revisa los totales: percepciones con su parte gravada y exenta, el total de otros pagos y de deducciones, el ISR retenido y cuántos recibos se van a timbrar. Si los recibos por timbrar cambian mientras revisas, la vista previa se actualiza y vuelves a confirmar. 4. Haz clic en **Timbrar**. Los recibos se timbran en segundo plano en los minutos siguientes. Cada vez que timbras se registra un lote en la tarjeta **Timbrado**, con la fecha de pago, quién lo inició y cuántos recibos se timbraron, siguen en proceso o tienen error. El botón **Timbrar** solo aparece mientras la corrida tenga recibos sin timbrar. ## Bandeja de timbrado [#bandeja-de-timbrado] Los recibos que timbras aparecen en **Nómina → Timbrado**. La bandeja tiene cuatro pestañas: * **Con error**: recibos que no se pudieron timbrar o cancelar, con la causa y cómo corregirla. * **En proceso**: recibos en cola, en envío o por confirmar. * **Timbrados**: recibos vigentes ante el SAT. * **Cancelados**: recibos cancelados ante el SAT, con su XML y su acuse. ![Pestaña Con error de la bandeja de timbrado con el botón Reintentar todos](/images/guides/cfdi/stamping-errors-toolbar.webp) ### Reintentar recibos con error [#reintentar-recibos-con-error] Corrige primero lo que indica la causa del error, por ejemplo los datos fiscales del empleado, y después reintenta: * **Un recibo**: abre el menú de opciones de su fila y elige **Reintentar**. * **Todos los recibos con error**: en la pestaña **Con error**, haz clic en **Reintentar todos**. * **Solo algunas causas**: abre la flecha junto a **Reintentar todos**, elige **Elegir causas**, marca las causas que quieres reintentar y haz clic en **Reintentar**. Cada causa muestra cuántos recibos tiene pendientes de reintento. ![Diálogo para reintentar recibos por causa con el número de recibos de cada una](/images/guides/cfdi/stamping-retry-by-cause.webp) Los recibos reintentados se timbran en los próximos minutos. Los que ya están en cola no se cuentan de nuevo, y los botones desaparecen cuando no queda ningún recibo por reintentar. Solo se reintentan los recibos de las razones sociales para las que tienes permiso de timbrar. Para ver únicamente una causa, usa **Filtros → Causa**. # Usuarios y permisos (/docs/configuration/access) Aquí administras quién entra a la plataforma y qué servicios externos se conectan a tu organización. Los usuarios y los roles están en **Configuración → Usuarios y permisos**; las API keys y las integraciones, en el grupo **Plataforma** del mismo menú. Los empleados que solo consultan su información en el portal de colaborador no necesitan ser usuarios: entran con el correo o el teléfono registrado en su expediente. ## Usuarios [#usuarios] **Configuración → Usuarios y permisos → Usuarios** muestra a los usuarios de la organización con su **Rol**, si es **Propietario** y su **Fecha de ingreso**. La pestaña **Invitaciones** muestra las invitaciones que todavía no se aceptan, con su **Estatus** (**Pendiente** o **Expirada**) y la fecha en que **Expira**. ![Lista de usuarios de la organización](/images/guides/configuration/members.webp) ### Invitar a un usuario [#invitar] 1. Haz clic en **Invitar usuario**. Se abre la página de invitación. 2. Escribe su **Correo electrónico**. 3. Si quieres que sea **Propietario de la organización**, marca la casilla. Solo un propietario puede otorgarlo. 4. En **Acceso**, elige de dónde vienen sus permisos: * **Rol asignado**: elige un **Rol**. Abajo se muestran los permisos que concede ese rol. * **Permisos propios**: define los permisos de esta persona, módulo por módulo, igual que en un rol. Si antes elegiste un rol, se parte de sus permisos. 5. Haz clic en **Enviar invitación**. ![Página para invitar a un usuario](/images/guides/configuration/invite-member.webp) La persona recibe un correo con un enlace para unirse a la organización: * Si todavía no tiene cuenta, la crea en esa misma página. * Si ya tiene cuenta, inicia sesión ahí mismo con el correo invitado y la invitación se acepta al entrar. * Si tiene abierta una sesión con otro correo, la página le ofrece **Cambiar cuenta** para continuar con el correo invitado. Al aceptar, ve una confirmación con la organización y el acceso que recibió, y entra con **Entrar**. La invitación vence a las 48 horas. En la pestaña **Invitaciones**, el menú de opciones de cada invitación permite: * **Reenviar invitación**: envía un enlace nuevo con 48 horas más de vigencia. El enlace anterior deja de funcionar. * **Editar invitación**: cambia el rol, los permisos propios o si será propietario. No se envía un correo nuevo y el enlace sigue funcionando. El correo no se puede cambiar; para invitar otro correo, cancela esta invitación y envía una nueva. * **Cancelar invitación**: el enlace deja de funcionar. Desde el menú de opciones de un usuario puedes **Editar** su nombre, su acceso y si es propietario, o **Eliminar** su acceso a la organización. El correo y el teléfono los administra cada persona desde su cuenta. No puedes eliminarte a ti mismo. ## Roles y permisos [#roles] Un rol es un conjunto de permisos que se asigna a los usuarios. Se administran en **Configuración → Usuarios y permisos → Permisos**. La lista muestra el **Nombre** de cada rol con su descripción, cuántos **permisos** concede y cuántos **Usuarios** lo tienen. La organización empieza con cuatro roles, que puedes editar para ajustarlos a tu equipo: * **Administrador**: todos los permisos, incluidos los usuarios, los roles y las integraciones. * **Nómina**: configura y opera la nómina de principio a fin: calcula, cierra y timbra. También administra los empleados, sus créditos y sus incidencias. No administra usuarios ni integraciones. * **Recursos humanos**: administra los empleados, la estructura organizacional, la asistencia, las incidencias, los horarios y las vacaciones y permisos, que también autoriza. Ve la nómina, pero no la calcula, no la cierra ni la timbra. * **Consulta**: puede ver todos los módulos, excepto los sueldos. ![Lista de roles de la organización](/images/guides/configuration/roles.webp) Para crear un rol, haz clic en **Crear rol** y escribe su **Nombre** y, si quieres, una **Descripción**. Desde el menú de opciones de un rol puedes **Editar** o **Eliminar**. Un rol no se puede eliminar mientras tenga usuarios asignados o invitaciones pendientes: reasigna a esos usuarios y edita o cancela esas invitaciones primero. Los permisos se organizan por sección: **Capital Humano**, **Organización**, **Asistencia e incidencias**, **Nómina**, **Usuarios y permisos** y **Desarrolladores**. Elige una sección en la barra de secciones y, para cada módulo, define tres cosas: * **Nivel**: **Ninguno**, **Ver** o **Administrar**. Administrar incluye ver. * **Capacidades aparte**: algunas acciones se conceden por separado con una casilla, como **Ejecutar** y **Cerrar periodo** en las corridas de nómina, o **Autorizar** en vacaciones y permisos. Se activan cuando el módulo tiene al menos **Ver**. * **Alcance**: **Toda la organización** o, en los módulos que lo permiten, **Su razón social**, algunas **Razones sociales** o algunas **Unidades organizacionales**. Si acotas un permiso, elige sus razones sociales o unidades antes de guardar. ![Editor de permisos de un rol, con niveles por módulo y alcance](/images/guides/configuration/role-form.webp) Los botones **Ver**, **Administrar** y **Limpiar** aplican ese nivel a todos los módulos de la sección que tienes abierta. El ícono de información junto a cada módulo explica qué cubre. **Próximamente.** Hoy los roles solo determinan qué indicadores ve cada usuario en el **Dashboard**. En el resto de la plataforma, cualquier usuario de la organización puede ver y hacer todo, sin importar su rol. Los roles que configures se aplicarán a cada módulo conforme se active el control de permisos. ## API keys [#api-keys] Una API key permite que un servicio externo, como un reloj checador o tu sistema contable, se conecte a la organización a través de la API. Se administran en **Configuración → API keys**. 1. Haz clic en **Crear API key**. 2. Escribe un **Nombre** que identifique el servicio que la usará. 3. Elige el **Miembro** al que queda asociada. 4. Enciende los permisos que necesita. Están agrupados por módulo, por ejemplo **Empleados** o **Departamentos**; **Seleccionar todo** y **Seleccionar todos** encienden todos los permisos, o todos los de un módulo. 5. Si quieres que la llave venza, elige un **Vencimiento** de 30, 90, 180 o 365 días. Sin vencimiento, la llave sigue vigente hasta que la revoques o la elimines. 6. Haz clic en **Crear API key**. ![Lista de API keys de la organización](/images/guides/configuration/api-keys.webp) ![Formulario para crear una API key con sus permisos](/images/guides/configuration/api-key-form.webp) **La llave solo se muestra una vez.** Al crearla, cópiala y guárdala en un lugar seguro antes de cerrar el diálogo. Después ya no se puede volver a ver; si la pierdes, elimínala y crea otra. Da a cada llave solo los permisos que el servicio necesita. Desde el menú de opciones de la llave puedes **Editar** sus permisos, **Revocar** la llave o **Eliminarla**. Al revocarla, los servicios que la usan dejan de tener acceso de inmediato y la llave se conserva en la lista como **Revocada**. Al eliminarla, también pierden acceso y la llave desaparece de la lista; no se puede deshacer. La columna **Estado** indica si la llave está **Activa**, **Por vencer** (le quedan 7 días o menos), **En reemplazo**, **Vencida** o **Revocada**. Los dueños de la organización reciben un correo cuando a una llave le quedan 7 días y 1 día. La documentación técnica de la API está en **Desarrolladores**, en el mismo menú. ## Integraciones [#integraciones] En **Configuración → Integraciones** registras los servicios externos que se conectan a tu organización, con un **Nombre** y una **Descripción**. Haz clic en **Crear integración** para agregar una. ![Pantalla de integraciones sin registros](/images/guides/configuration/integrations.webp) Al abrir una integración, la parte superior muestra su **salud**: el **Estatus** según las llamadas de la última hora, el porcentaje de errores, la última llamada correcta y las llaves que han trabajado en las últimas 24 horas. Las llamadas incluyen los envíos de webhooks. El estatus es **Fallando** cuando la mitad o más de las llamadas de la última hora terminan en error, **Con fallas** desde el 10 %, **Operando** por debajo de eso y **Sin actividad** si no hubo llamadas. Las llamadas a la API rechazadas por falta de permiso se muestran aparte y nunca marcan la integración como fallando. En un webhook, cualquier respuesta fuera de 2xx o la falta de respuesta cuenta como error. El mismo estatus aparece en la lista de integraciones. La pestaña **API Keys** muestra las llaves emitidas para la integración, la pestaña **Permisos** los permisos que se le otorgaron, la pestaña **Webhooks** las direcciones a las que avisa y la pestaña **Logs** el historial de sus llamadas y de cada intento de envío de sus webhooks, marcados como **Webhook**. **Avisa a otro sistema cuando algo pasa.** En la pestaña **Webhooks**, haz clic en **Crear webhook**, escribe la **URL** del sistema que recibe el aviso y marca los **Eventos**: empleado creado, actualizado o dado de baja; periodo abierto, calculado o cerrado; recibo timbrado o con error de timbrado, y solicitud de tiempo libre aprobada. La URL debe empezar con `https://` y apuntar a una dirección pública. El campo **Activo** viene encendido; si lo apagas, el webhook se guarda en pausa. El **Secreto de firma** se muestra una sola vez: guárdalo en el sistema que recibe el aviso para que compruebe que viene de la plataforma. La integración solo recibe los eventos que puede consultar con sus permisos, y el formulario marca los eventos para los que no tiene permiso o que solo recibe para algunas razones sociales. Cada fila muestra su última entrega: **Entregado**, **Reintentando**, **Fallido** o **No enviado**. Si la dirección falla, el envío se reintenta a 1 minuto, 5 minutos, 30 minutos, 2 horas y 6 horas; al sexto intento fallido queda en **Fallido**. Desde el menú de la fila puedes **Editar**, **Pausar** o **Eliminar** el webhook; al editarlo también puedes pausarlo o reactivarlo con el campo **Activo**. Mientras está en pausa no recibe eventos nuevos y sus avisos pendientes esperan hasta que haces clic en **Reactivar**; un envío que iba a reintentarse se muestra **En espera**. Si dejas de marcar un evento, sus avisos pendientes se descartan. Al eliminarlo, sus avisos pendientes se descartan y los intentos ya enviados siguen en **Logs**. Lo que el sistema receptor necesita saber está en [Webhooks](/docs/developers/webhooks). **Rota una llave sin interrumpir el servicio.** En el menú de opciones de una llave de integración vigente, elige **Rotar**. Se crea una llave nueva con los mismos permisos y la misma vigencia que tenía la anterior, y se muestra una sola vez. La llave anterior sigue funcionando durante el **Periodo de transición** que elijas (1, 7 o 30 días), tiempo en el que actualizas el servicio con la llave nueva. Pasado ese periodo la anterior deja de funcionar; puedes **Revocarla** antes si ya no la necesitas. Una llave solo se puede rotar una vez. # Calendarios de nómina (/docs/configuration/calendars) El calendario define cada cuánto se paga, con qué tabla se retiene el ISR y genera los periodos del año. Cada empleado se paga con el calendario que tenga asignado, y cada calendario pertenece a una razón social. Se administran en **Configuración → Nómina → Calendarios**. ![Tabla de calendarios de nómina](/images/guides/configuration/payroll-calendars-table.webp) ## Crear un calendario [#crear] 1. Haz clic en **Crear calendario**. 2. Selecciona las **Razones sociales**. Se crea un calendario igual en cada una. 3. Escribe el **Nombre del calendario**. No puede repetirse dentro de la misma razón social. 4. Elige la **Periodicidad**. 5. Elige la **Tabla de ISR**. 6. Decide si dejas marcada la casilla **Aplicar descuento de séptimo día proporcional**. Viene marcada; consulta [El séptimo día proporcional](#septimo-dia). 7. Elige los **Días del mes**: **30 DÍAS** o **30.4 DÍAS**. Consulta [Los días del mes](#dias-del-mes). 8. En un calendario semanal o catorcenal, elige el **Inicio del primer periodo** si no quieres que empiece el 1 de enero. Consulta [Cuándo inicia el primer periodo](#inicio-primer-periodo). 9. Revisa el **Año** y los **Días para pago**. Consulta [Los días para pago](#dias-para-pago). 10. Haz clic en **Crear calendario**. ![Formulario para crear un calendario de nómina quincenal](/images/guides/getting-started/payroll-calendar-form.webp) ## Periodicidades [#periodicidades] | Periodicidad | Periodos | Cómo se arman | | ------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Diaria | Uno por día del año | Cada día es un periodo. | | Semanal | 52 | Bloques de 7 días a partir del **Inicio del primer periodo** (el 1 de enero si no lo modificas); cada año nuevo continúa desde el día siguiente al último periodo. | | Decenal | 36 | Del 1 al 10, del 11 al 20 y del 21 al fin de mes. | | Catorcenal | 26 | Bloques de 14 días a partir del **Inicio del primer periodo** (el 1 de enero si no lo modificas); cada año nuevo continúa desde el día siguiente al último periodo. | | Quincenal | 24 | Del 1 al 15 y del 16 al fin de mes. | | Mensual | 12 | Del primer al último día de cada mes. | La fecha de pago de cada periodo es su último día más los **Días para pago**, contados en días naturales. ## Cuándo inicia el primer periodo [#inicio-primer-periodo] En los calendarios semanales y catorcenales, el campo opcional **Inicio del primer periodo** fija el día en que empieza el primer periodo del año. Puedes elegir cualquier día entre el 1 de diciembre del año anterior y el 31 de enero del **Año** del calendario. Si lo dejas vacío, el primer periodo empieza el 1 de enero. Las demás periodicidades siguen el calendario natural y no tienen este campo. ## Los días para pago [#dias-para-pago] Los **Días para pago** van de -15 a 15. Un valor positivo paga después del cierre del periodo: con 5, un periodo que cierra el día 15 se paga el día 20. Un valor negativo paga antes del cierre: con -2, un periodo que cierra el domingo se paga el viernes. El pago nunca puede caer antes del inicio del periodo, por eso cada periodicidad tiene un mínimo: | Periodicidad | Mínimo de días para pago | | ------------ | ------------------------ | | Diaria | 0 | | Semanal | -6 | | Decenal | -7 | | Catorcenal | -13 | | Quincenal | -12 | | Mensual | -15 | En el detalle del calendario, un valor negativo se lee como *Pago N día(s) antes del cierre*. ## El mes de acumulación [#mes-de-acumulacion] Cada periodo tiene un **Mes de acumulación**: el mes en que cuenta para la ventana mensual de ISR, es decir, el mes cuyos periodos se acumulan en la tarifa mensual. En los calendarios semanales y catorcenales nuevos, el mes de acumulación es el mes de la fecha de pago. En las demás periodicidades es el mes de la fecha de fin. Si el periodo cae fuera del año fiscal, el mes es enero o diciembre. Los periodos que se crearon antes de que existiera este campo no guardan un mes y cuentan en el mes de su fecha de fin. Puedes modificarlo al [editar el periodo](#editar-periodo). ## La tabla de ISR [#tabla-de-isr] La tabla de ISR decide cómo se calcula la retención de cada periodo. No se deduce de la periodicidad: la eliges tú. Hay dos opciones. **La tabla de la misma periodicidad.** Por ejemplo, **Quincenal (15 días)** para un calendario quincenal. Cada periodo se calcula solo, con la tabla del número de días del periodo. No se acumula el mes. **Mensual (acumulada).** Lo que el empleado ha ganado en el mes se proyecta a un mes completo, con los días del mes del calendario (30 o 30.4), se le aplica la tarifa mensual por los días ya pagados y se le resta lo que ya se retuvo en los periodos anteriores del mismo mes. Así, cada periodo corrige lo que se haya retenido de más o de menos en los anteriores del mismo mes. El detalle está en [ISR y subsidio para el empleo](/docs/calculation/isr-and-subsidy#que-tarifa). En un calendario mensual, la única opción es la mensual. ## Los días del mes [#dias-del-mes] Los **Días del mes** del calendario son 30 o 30.4. Con ellos se convierte a diario el sueldo mensual de los empleados del calendario, se fijan los días que paga una nómina mensual y se proyecta el mes en la tarifa mensual del ISR. Cada calendario tiene los suyos. El catálogo **Factor de días por semana/quincena/mes/año** solo sugiere el valor al crear un calendario nuevo; cambiarlo no modifica los calendarios que ya existen. Si eliges varias razones sociales y todas sugieren el mismo valor, el campo viene lleno; si no coinciden, el campo viene vacío y cada calendario toma el valor de su razón social. Si el catálogo tiene un mes distinto de 30 y 30.4, el calendario nuevo empieza en 30. Los calendarios que ya existían conservan el mes que tenía su razón social. Si ese valor no es 30 ni 30.4 (por ejemplo, 31), el campo lo muestra como texto de apoyo al editar, y se conserva si guardas sin elegir otro. ## El séptimo día proporcional [#septimo-dia] El día de descanso semanal se gana con los días trabajados. La casilla **Aplicar descuento de séptimo día proporcional** decide si las faltas, las faltas justificadas y las ausencias sin goce también descuentan una parte de ese día. **Marcada (así viene).** Por cada día sin sueldo se descuenta además un sexto de día. En el recibo aparece la deducción **Séptimo día proporcional**. El detalle está en [Sueldo y días](/docs/calculation/salary-and-days). **Desmarcada.** El día de descanso se paga completo aunque haya faltas, faltas justificadas o ausencias sin goce. Los días faltados se siguen descontando en la línea **Faltas**, **Falta justificada** o **Ausencia sin goce de sueldo**; solo deja de aparecer la deducción del séptimo día. La casilla aplica a todos los periodos del calendario, al finiquito de sus empleados y a los recibos que se timbran. ## Qué puedes cambiar después [#cambios] Una vez creado, la **Periodicidad** ya no se puede cambiar. Si necesitas otra, crea un calendario nuevo. Para pagar en otro año no hace falta un calendario nuevo: [genera sus periodos](#generar-periodos). Desde **Editar**, en el menú de opciones del calendario, puedes cambiar: * El **Nombre del calendario**. * Los **Días para pago**, dentro del mínimo de su periodicidad. * La **Tabla de ISR**. * Los **Días del mes**. * La casilla **Aplicar descuento de séptimo día proporcional**. Cambiar la tabla de ISR, los días del mes o el séptimo día no modifica los periodos cerrados. Los periodos abiertos o calculados toman el cambio cuando los recalcules. **Revisa el aviso de los días del mes.** Si el calendario tiene nóminas en borrador, calculándose o calculadas y eliges otros días del mes, el formulario muestra el aviso *El calendario tiene periodos abiertos o calculados*. Al guardar, esas nóminas toman el nuevo valor cuando las recalcules. **Cambia la tabla de ISR con el mes completo cerrado.** Si un mes ya tiene un periodo cerrado con la tabla mensual y otro todavía abierto, el formulario te avisa: si cambias la tabla en ese momento, el periodo abierto retendrá menos ISR del que corresponde y la diferencia se corregirá hasta el ajuste anual. Espera a que todos los periodos de ese mes estén cerrados. ## Ver los periodos [#periodos] Haz clic en el nombre del calendario para abrir su detalle. Ahí ves la razón social, la periodicidad, el número de periodos y los días para pago. Los periodos se agrupan por año en secciones que puedes abrir y cerrar; el año en curso aparece abierto. Cada periodo muestra su número, **Fecha inicio**, **Fecha fin**, **Fecha de pago**, **Días**, **Mes de acumulación** y **Estatus**. ![Detalle de un calendario quincenal con sus periodos](/images/guides/configuration/calendar-detail.webp) Los periodos se abren, calculan y cierran desde **Nómina → Cálculo**. ## Editar un periodo [#editar-periodo] Un periodo que todavía no tiene nómina se puede ajustar, por ejemplo para adelantar una fecha de pago que cae en día festivo. Abre el menú de opciones (⋮) de su fila, o haz clic derecho sobre ella, y elige **Editar**. Puedes modificar la **Fecha de inicio**, la **Fecha de fin**, la **Fecha de pago** y el **Mes de acumulación**. **No se puede editar si:** * El periodo ya tiene una nómina. * Las fechas se cruzan con el periodo anterior o el siguiente. * Las fechas caen fuera del 1 de diciembre del año anterior al 31 de enero del año siguiente. * La fecha de pago es anterior al inicio del periodo o queda más de 15 días después de su fin. * Modificas el mes de acumulación y el mes anterior o el nuevo ya tiene nóminas calculándose, calculadas o cerradas en ese calendario. **El mes de los vales.** Al modificar el **Mes de acumulación**, el mes en que el periodo otorga los vales de despensa se mueve con él. Si solo editas las fechas, ese mes no se modifica. ## Agregar un periodo [#agregar-periodo] Algunos años semanales o catorcenales tienen una semana o catorcena más. En esos calendarios, haz clic en **Agregar periodo**, en el detalle del calendario, para sumar un periodo al final del último año. Empieza el día siguiente al fin del último periodo, dura lo mismo que los demás y su fecha de pago sigue los días para pago del calendario. Después puedes ajustar sus fechas con [Editar](#editar-periodo). No se puede agregar si el mes de acumulación del periodo nuevo ya tiene nóminas calculándose, calculadas o cerradas, ni si el periodo nuevo terminaría después del 31 de enero del año siguiente. ## Generar periodos de otro año [#generar-periodos] Un calendario se crea con los periodos de un solo año. Para seguir pagando en otro año, sin crear un calendario nuevo ni reasignar empleados: 1. Abre el detalle del calendario. 2. Haz clic en **Generar periodos**. 3. Revisa el **Año**. Se propone el año siguiente al último que tiene el calendario. 4. Haz clic en **Generar periodos**. Los periodos nuevos siguen la periodicidad y los días para pago del calendario, y los empleados siguen con el mismo calendario. Si el año ya tiene periodos, aparece el aviso *Ese año ya tiene periodos generados.* y no se duplica nada. En los calendarios semanales y catorcenales solo puedes generar el año siguiente al último, porque cada año continúa desde el día siguiente al último periodo, incluido uno que hayas agregado. Si el año se salta uno, genera primero el que falta. Si la tarifa de ISR del año elegido todavía no está publicada, el diálogo te avisa. Aun así puedes generar los periodos: al abrir un periodo se usan los parámetros fiscales vigentes en ese momento, y si se publican cambios, los periodos abiertos se recalculan. ## Desactivar o eliminar [#desactivar-o-eliminar] Desde el menú de opciones puedes **Desactivar**, **Activar** o **Eliminar** un calendario. **Solo sin empleados vinculados.** No puedes desactivar ni eliminar un calendario que tenga empleados asignados. Asígnales otro calendario antes de continuar. # Conceptos de nómina (/docs/configuration/concepts) Los conceptos son los renglones del recibo: el sueldo, las horas extra, el ISR, las cuotas del IMSS, los vales, los préstamos. El catálogo de la organización se crea con la primera razón social e incluye los conceptos de ley y un grupo de conceptos extralegales de uso común. Más adelante la plataforma puede agregar conceptos nuevos a tu catálogo; te avisa por correo y aparecen en esta misma tabla. Se administran en **Configuración → Incidencias → Conceptos**. ![Tabla de conceptos de nómina](/images/guides/configuration/concepts-table.webp) ## Qué muestra la tabla [#tabla] * **Código**: el código interno del concepto, por ejemplo P001 para el sueldo o D001 para el ISR. * **Tipo**: **Percepción**, **Deducción** o **Informativo**. * **ISR** y **SBC**: cómo entra el concepto a esas dos bases, por ejemplo **Gravado**, **Exento hasta 15 UMA/año** o **No integra**. * **Integración al SDI**: en las percepciones, si integran al SBC como **Fija**, **Variable**, **Mixta** o **No integra**. Consulta [Tratamiento fiscal](#tratamiento-fiscal). * **Administración**: en los conceptos de ley, si los mantiene la **Plataforma** o tu organización (**Autoadministrado**). * **Difiere**: marca los conceptos cuyo tratamiento fiscal cambiaste respecto al que sugiere la plataforma. * **Origen**: **De ley** o **Extralegal**. * **Estatus**: activo o inactivo. Usa la búsqueda por nombre o código y el botón **Filtros** para acotar la lista. ## Tipos de concepto [#tipos] * **Percepción**: un pago a favor del empleado, como sueldo, bonos, comisiones u horas extra. * **Deducción**: un descuento al empleado, como el ISR, las cuotas del IMSS o un préstamo. * **Informativo**: algo que se calcula o reporta sin ser pago ni descuento. Tiene cuatro subtipos: * **Otro pago**: aparece en el recibo y se timbra, como los viáticos. No es gravado ni exento. * **Obligación patronal**: un costo a cargo de la empresa que no se descuenta al empleado, como el IMSS patronal, el INFONAVIT o el ISN. * **Provisión**: provisiones y costo total; nunca aparecen en el recibo. * **Interno**: cálculos internos de la organización; nunca aparecen en el recibo. ## De ley y extralegales [#origen] **De ley.** Obligaciones de la Ley Federal del Trabajo, del IMSS o del ISR. Son de solo lectura: no se editan, no se desactivan y no se eliminan. La plataforma los mantiene al día. **Extralegal.** Conceptos propios de la organización, ya sea del catálogo inicial o creados por ti. Puedes editarlos, desactivarlos y eliminarlos. Si quieres pagar más de lo que marca la ley en un concepto de ley, por ejemplo más días de aguinaldo, no lo modifiques: crea un concepto adicional, como se explica más abajo. ## Código y clave SAT [#codigo-y-clave-sat] Son dos datos distintos y los dos se usan: * El **Código** identifica el concepto dentro de la plataforma y en el recibo, por ejemplo P001. Lo defines tú en los conceptos propios y no puede repetirse. El nombre sí puede repetirse. Si la plataforma agrega un concepto con un código que ya usa uno de tus conceptos propios, el tuyo no cambia y el nuevo llega con el siguiente código libre de la misma letra, por ejemplo P151 en lugar de P150. * La **Clave SAT** es la clave del catálogo del SAT con la que el concepto se reporta en el recibo timbrado, por ejemplo 001 para sueldos. Varios conceptos pueden compartir la misma clave SAT. Si el concepto se timbra como percepción, deducción u otro pago depende del **Tipo** y del subtipo; el ícono junto a **Clave SAT** te lo indica. Los informativos de tipo obligación patronal, provisión e interno no se timbran y no llevan clave SAT. ## De dónde sale el importe [#calculo] Cada concepto se calcula de una de tres formas: * **Fórmula de ley**: la calcula la plataforma con la fórmula legal. No se modifica. * **Fórmula propia**: se calcula con una fórmula que define tu organización. * **Captura manual**: el importe se captura cada periodo por empleado, desde la pestaña **Conceptos** de **Incidencias → Asistencia**. Los créditos del empleado, como INFONAVIT, FONACOT, pensión alimenticia o préstamos, toman el importe del crédito registrado a cada empleado y se detienen al liquidarse. Para ver cómo se obtiene el importe de cualquier concepto, abre su menú de opciones y elige **Cálculo**. ![Menú de opciones de un concepto de ley](/images/guides/configuration/concept-actions-menu.webp) ## Quién recibe cada concepto [#se-aplica-a] * **De ley que calcula la plataforma**, como el sueldo, el ISR o el IMSS: se calculan a cada empleado al que aplican, sin configurar nada. * **Extralegales**: los recibe automáticamente quien tenga el concepto en su contrato, como vales o fondo de ahorro. Se asignan en [Contratos](/docs/configuration/contracts#conceptos-extralegales). * **Importes capturados**: cualquier concepto activo de captura manual, salvo los créditos, se puede capturar a cualquier empleado en el periodo, esté o no en su contrato. Un concepto de fórmula también se puede capturar a los empleados cuyo contrato lo tiene en **Personalizado** con **Captura manual**. Así pagas, por ejemplo, un bono a una sola persona sin modificar su contrato. * **Créditos**: solo aplican a quien tenga el adeudo registrado, como un crédito INFONAVIT o un préstamo. ## Tratamiento fiscal [#tratamiento-fiscal] Abre el menú de opciones del concepto y elige **Tratamiento fiscal** para ver cómo entra a cada base de cálculo: * **ISR**: si paga impuesto sobre la renta. Puede ser **Gravado**, **Exento**, **Parcial** (solo un porcentaje grava) o **Hasta tope** (exento hasta el tope legal; el excedente grava). * **SBC · IMSS**: si integra al salario base de cotización. Si integra, sube las cuotas del IMSS. * **SBC · INFONAVIT**: si integra para la aportación de vivienda. * **SDI (LFT)**: si cuenta para finiquitos e indemnizaciones. * **ISN**: si causa impuesto sobre nómina. * **PTU**: si forma parte de la base del reparto de utilidades. El ícono junto a cada base explica qué mide. ![Tratamiento fiscal del concepto Sueldo](/images/guides/configuration/concept-tax-treatment.webp) Qué puedes cambiar depende del concepto: * **Extralegales**: todas las bases son editables. Cada una muestra **Sugerido** si sigue la sugerencia de la plataforma o **Personalizado** si la cambiaste; **↺** la regresa a la sugerencia. * **De ley que calcula la plataforma**, como el sueldo: nada es editable. Sus valores cambian cuando cambia la ley, con las actualizaciones de la plataforma. * **Otros conceptos de ley**: el SBC, el SDI y la PTU no se editan. El ISN sí, incluso por estado. El ISR solo se puede cambiar si tu organización toma la administración del concepto. * **Otros pagos**: no afectan ninguna base, así que nada es editable. **Integración al SDI.** Cada percepción indica además cómo integra al salario base de cotización: **Fija**, no entra al promedio (el factor de integración solo lleva el aguinaldo y la prima vacacional); **Variable** o **Mixta**, con el [promedio bimestral del pago variable](/docs/calculation/sbc#parte-variable), por la parte que integra su base **SDI (LFT)**; o **No integra**. Los conceptos de ley ya traen la suya. Un concepto propio nace con integración fija; la cambias en el campo **Integración al SDI** de su formulario, que solo aparece en las percepciones; si cambias el tipo, vuelve a la integración guardada o a **Fija**. Las deducciones y los informativos nunca integran. ### Administrar un concepto de ley [#autoadministrar] En algunos conceptos de ley, el menú de opciones ofrece **Administrar este concepto**. Al tomar la administración, tu organización se hace responsable de mantener el concepto conforme a la ley y recibirás un correo cada vez que la plataforma actualice la configuración sugerida. Para confirmar, marca la casilla de responsabilidad y haz clic en **Tomar administración**. **Los cambios al ISR requieren un segundo administrador.** Cuando cambias el tratamiento de ISR de un concepto autoadministrado, el cambio queda pendiente hasta que otro administrador lo aprueba con **Aprobar cambio**. Mientras tanto, la nómina sigue usando el valor vigente. Quien propone el cambio no puede aprobarlo. Para devolver el concepto a la plataforma, elige **Volver a administración de la plataforma** y confirma con **Restaurar valores de ley**. El concepto regresa a la configuración sugerida. ## Crear un concepto propio [#crear] 1. Haz clic en **Crear concepto**. 2. Escribe el **Código** y elige el **Tipo**. Si es informativo, elige también el **Subtipo**. 3. Escribe el **Nombre (mostrado en recibos)**. 4. Si el concepto se timbra, elige la **Clave SAT**. 5. Si es percepción, elige su **Integración al SDI**. 6. En **Captura**, elige **Fórmula propia** o **Captura manual**. 7. Indica el **Orden** en que aparece. 8. Marca las **Opciones** que apliquen. 9. Si elegiste fórmula propia, escríbela en **Fórmula**. 10. Haz clic en **Crear concepto**. ![Formulario para crear un concepto de nómina](/images/guides/configuration/concept-create-form.webp) Las opciones son: * **Responsabilidad patronal**: es un costo de la empresa; suma al costo y no se descuenta al empleado. * **Se suma al neto a pagar**: el importe forma parte del depósito al empleado. Desmárcala si el valor se entrega por otro medio, como una tarjeta de vales o en especie. El concepto sigue en el recibo y en las bases de ISR e IMSS, pero queda fuera del depósito. * **Reduce la base de ISR**: solo en deducciones propias con fórmula o captura manual. El importe se resta de la base gravable antes de calcular el ISR y el subsidio para el empleo, y se sigue descontando del neto. * **Reduce la base de ISN**: solo en deducciones propias con fórmula o captura manual. El importe se resta de la base del impuesto sobre nómina del estado, y se sigue descontando del neto. Los cambios a un concepto (su fórmula, sus casillas y sus bases) aplican también a los periodos abiertos en cuanto recalculas la nómina. Si desactivas un concepto, deja de aparecer en esos recibos al recalcular, salvo en los periodos donde ya tiene importes capturados: ahí se conserva con la definición que tenía al abrir el periodo. Las nóminas cerradas conservan los importes con los que se cerraron. El editor de fórmulas te ofrece las variables disponibles, agrupadas en datos del empleado, parámetros fiscales, incidencias del periodo y tablas fiscales, además de funciones. La fórmula se revisa mientras escribes y se evalúa por empleado con los datos del periodo. Un concepto nuevo empieza con un tratamiento fiscal por omisión: si es percepción, entra completo a todas las bases; si es deducción o informativo, no entra a ninguna. Ajusta cada base después en **Tratamiento fiscal**. ## Crear un concepto adicional [#concepto-adicional] Para pagar por encima del mínimo de un concepto de ley, por ejemplo más días de aguinaldo que los de ley, abre el menú del concepto y elige **Crear concepto adicional basado en éste**. 1. Escribe el **Código** y el **Nombre (mostrado en recibos)** del adicional. 2. En **Captura**, deja la fórmula que la plataforma propone a partir del concepto original y ajústala, o cambia a **Captura manual**. 3. Guarda. El adicional es un concepto extralegal del mismo tipo que el original. Se timbra con la clave SAT 038, Otros ingresos por salarios, si es percepción, o 004, Otros, si es deducción, y nace gravado en todas las bases: las exenciones de ley corresponden al concepto de ley, no a lo que tu empresa otorga por su cuenta. Puedes ajustar cada base después en **Tratamiento fiscal**. No se puede crear un adicional de otro adicional. ## Desactivar o eliminar [#desactivar-o-eliminar] Solo los conceptos extralegales se desactivan o eliminan, desde su menú de opciones. * **Desactivar**: el concepto deja de estar disponible para nuevos contratos y deja de aplicarse en los próximos cálculos. El diálogo te dice a cuántos empleados afecta. Puedes volver a activarlo cuando quieras. * **Eliminar**: lo quita del catálogo y no se puede deshacer. No se permite mientras algún empleado lo tenga en su contrato ni mientras un periodo abierto lo use. **Desc Patronal Fondo Ahorro** descuenta la aportación del patrón y **Vales de despensa entregados** descuenta los vales que ya se entregaron en tarjeta. Ninguno de los dos se puede eliminar, y no se pueden desactivar mientras **Desc Aportación Trabajador F.A** o **Vales de despensa**, respectivamente, estén activos: su menú de opciones no muestra esas acciones. Cuando vuelves a activar **Desc Aportación Trabajador F.A** o **Vales de despensa**, su deducción se activa con ellos. # Contratos (/docs/configuration/contracts) El contrato registra el tipo de relación laboral que se reporta al SAT, los conceptos extralegales que aplican a los empleados que lo tienen, como vales de despensa o fondo de ahorro, y sus prestaciones: aguinaldo, vacaciones, primas, vales y fondo de ahorro. Cada empleado tiene asignado un contrato. Se administran en **Configuración → Nómina → Contratos**. Toda razón social queda vinculada al **Contrato base (indeterminado)**, de tipo 01, Contrato por tiempo indeterminado. Se crea con las prestaciones mínimas de ley (aguinaldo de 15 días, prima vacacional de 25 % y vacaciones según la tabla de ley) y con todos los conceptos extralegales activos del catálogo. Si todos tus empleados tienen el mismo tipo de contrato y las mismas prestaciones, con ese basta. ![Tabla de contratos con el contrato base](/images/guides/getting-started/contracts-table.webp) ## Crear un contrato [#crear] 1. Haz clic en **Crear contrato**. Se abre la página del contrato nuevo. 2. Elige las **Razones sociales** donde aplica. 3. Escribe el **Nombre**. No puede repetirse. 4. Elige el **Tipo de contrato** del catálogo del SAT. 5. Si quieres, agrega una **Descripción**. 6. Revisa las **Prestaciones**. Empiezan con el mínimo de ley. 7. En **Conceptos extralegales**, busca y agrega los conceptos que reciben los empleados con este contrato. 8. Haz clic en **Crear contrato**. Regresas a la tabla de contratos. La tabla muestra, para cada contrato, su tipo, cuántos conceptos tiene y cuántos empleados lo utilizan. ## Página del contrato [#pagina] Cada contrato tiene su propia página. Ábrela con un clic en su nombre o con **Editar** en el menú de opciones. Arriba ves su número, razones sociales, tipo, cuántos conceptos y empleados tiene y su estatus; debajo, **Datos generales**, **Prestaciones** y **Conceptos**. Modifica lo que necesites y haz clic en **Guardar**. Al guardar regresas a la tabla de contratos. Si algún campo tiene un error, aparece el aviso **Revisa los campos marcados e intenta de nuevo.** y la página te lleva al primer campo marcado. Para regresar sin guardar, haz clic en **Cancelar**. Si tu usuario solo puede consultar la configuración de nómina, la página muestra los mismos datos como texto, sin **Guardar**. ## Prestaciones [#prestaciones] Las prestaciones del contrato son las que la nómina utiliza para todos sus empleados, en cualquiera de sus razones sociales. Si una razón social necesita prestaciones distintas, crea un contrato para ella; puedes partir de una copia con [Duplicar](#duplicar). Para modificarlas, abre la [página del contrato](#pagina). * **Aguinaldo** y **Vacaciones**: tablas de días por años cumplidos que empiezan en 0 años, en renglones con **Desde (años)**, **Hasta (años)** y **Días**. Cada renglón cubre un rango, sin huecos, y el último queda sin límite. El aguinaldo no puede ser menor a 15 días y las vacaciones del primer año no pueden ser menores a 12 días. Si el aguinaldo crece con la antigüedad, agrega un renglón por cada escalón, por ejemplo 0 a 0: 15 días, 1 a 7: 20 días y 8 en adelante: 30 días. El renglón de 0 años de vacaciones son las vacaciones del primer año de servicio. La tabla de ley es: 0 años, 12 días; 1, 14; 2, 16; 3, 18; 4, 20; de 5 a 9, 22; de 10 a 14, 24; de 15 a 19, 26; de 20 a 24, 28; de 25 en adelante, 30. * **Considerar ausentismo en el aguinaldo**: opción debajo de la tabla de aguinaldo, activada por omisión. Descuenta de los días trabajados del año las faltas injustificadas, los permisos y las vacaciones sin goce y las incapacidades por enfermedad general, tanto en la nómina de aguinaldo como en el finiquito; las incapacidades por riesgo de trabajo y la maternidad siguen contando como tiempo laborado. Si lo desactivas, no se descuenta nada. Es una opción del contrato. * **Prima vacacional** y **Prima dominical**: proporción entre 0.25 y 1. Junto a cada una ves si está en el **Mínimo de ley**, **Superior al mínimo de ley** o **Por debajo del mínimo**, y puedes regresarla al mínimo. * **Pago de prima vacacional**: **Conforme se disfrutan los días**, **En el aniversario del año de servicio** o **No se calcula**. Con **No se calcula**, la nómina y el finiquito no pagan la prima vacacional, el **Factor de integración (SBC)** no la incluye y el campo **Prima vacacional** desaparece del formulario y de las pantallas del empleado. Si vuelves a otra opción, el campo regresa con el porcentaje que tenía. * **Vales de despensa**: tienen un **Tipo de cálculo**: * **% del sueldo**: el porcentaje como proporción (0.10 es 10 %). Los vales del mes son el sueldo diario por 30 días por ese porcentaje. * **Monto fijo mensual**: el **Monto mensual** en pesos. También tienen un **Método de entrega**: con **Tarjeta electrónica** los vales aparecen en el recibo como la percepción **Vales de despensa** y se descuentan en la deducción **Vales de despensa entregados**, así que no suman al neto a depositar; con **Efectivo** aparecen como **Vales de despensa (efectivo)** y sí suman al neto. La opción **Limitar los vales al tope exento del IMSS** aplica cuando la empresa entrega como máximo el monto que no integra al SBC: el 40 % de la UMA diaria por 30.4 días ($1,426.49 en 2026). Los vales del mes, por porcentaje o por monto fijo, no pasan de ese tope, que se actualiza solo cuando cambia la UMA. Si el concepto de vales de despensa tiene su propio tope para el IMSS, se respeta ese tope. Sin la opción, la parte de los vales que rebasa el tope sí integra al SBC. Por último, tienen una **Frecuencia de otorgamiento**; consulta [Cuándo se pagan los vales](#frecuencia-de-vales). * **Fondo de ahorro**: la aportación del trabajador es una proporción de hasta 0.13 (13 %) y la del patrón debe ser igual. Además tiene una **Frecuencia de retiros**: **Una vez al año**, **Dos veces al año** o **Más de dos veces al año**. El fondo de ahorro queda fuera del SBC solo si la aportación del patrón es igual a la del trabajador y los retiros se permiten a lo más dos veces al año. La plataforma valida la primera condición; la segunda depende de cómo opera el fondo tu empresa, así que la plataforma solo la registra. Con **Más de dos veces al año**, el formulario muestra un aviso, pero la nómina sigue calculando el fondo como exento. En el recibo, la aportación del patrón aparece como la percepción **Aport Patronal Fondo Ahorro**, y cada aportación se descuenta en su propia deducción: **Desc Aportación Trabajador F.A** y **Desc Patronal Fondo Ahorro**. Como la aportación del patrón entra y sale en el mismo recibo, el neto a depositar es el mismo que si solo se descontara la del trabajador. Los recibos anteriores a la separación muestran una sola deducción, **Fondo de ahorro**, con las dos aportaciones. Debajo ves el **Factor de integración (SBC)** que resulta de esas prestaciones. Los cambios llegan a las nóminas abiertas cuando las recalculas; una nómina cerrada conserva lo que se calculó. ## Cuándo se pagan los vales [#frecuencia-de-vales] La **Frecuencia de otorgamiento** de los vales de despensa tiene dos opciones: * **En cada nómina**: el monto del mes se reparte entre las nóminas del mes: la mitad en cada quincena, o el monto entre las semanas del mes en una nómina semanal. Si el periodo no se trabaja completo, se paga la parte proporcional. * **Una vez al mes**: una sola nómina del mes paga los vales del mes completo, y las demás nóminas del mes no llevan vales. El tope exento es el del mes completo, sin dividirlo entre las nóminas. Con **Una vez al mes** se define qué nómina paga los vales en cada tipo de calendario. Los cuatro campos son obligatorios, aunque la prestación solo se aplique en un tipo de calendario: | Campo | Opciones | | ------------------------------------------ | ---------------------------------------------------------------------------------- | | **Semana de pago en nómina semanal** | **Semana 1**, **Semana 2**, **Semana 3**, **Semana 4** o **Última semana del mes** | | **Quincena de pago en nómina quincenal** | **Primera quincena** o **Segunda quincena** | | **Catorcena de pago en nómina catorcenal** | **Catorcena 1**, **Catorcena 2** o **Última catorcena del mes** | | **Decena de pago en nómina decenal** | **Primera decena**, **Segunda decena** o **Tercera decena** | Si el mes tiene menos periodos que el elegido, paga el último periodo del mes. En una nómina mensual, cada nómina paga los vales de su mes; en una nómina diaria, los paga el último día del mes. **A qué mes pertenece cada periodo.** Una semana o una catorcena puede caer entre dos meses. Cuenta en su **Mes de acumulación**: en los calendarios semanales y catorcenales es por defecto el mes de la fecha de pago, y en las demás periodicidades el mes de la fecha de fin; los periodos creados antes conservan el mes en que terminan. Los vales de despensa siguen el mes del periodo. El mes de nómina abarca desde el inicio de su primer periodo hasta el fin del último. **Ingresos y bajas a mitad de mes.** Si el empleado no trabajó todo el mes de nómina, la nómina que paga los vales le paga la parte proporcional a los días que estuvo contratado en ese mes. Si ingresó después del periodo que paga los vales, se los paga la nómina del periodo de su ingreso. En un finiquito, los vales del mes se pagan en proporción a los días trabajados hasta la baja, salvo que el empleado siga en una nómina del periodo que paga los vales de ese mes: en ese caso los vales ya se pagan ahí y el finiquito no los repite. ## Cómo se aplica la antigüedad [#antiguedad] Cada empleado recibe, de su contrato, los días del renglón que corresponde a sus años cumplidos de antigüedad, contados desde su fecha de antigüedad reconocida o, si no tiene, desde su fecha de ingreso: * En la nómina ordinaria, a la fecha de fin del periodo. * En la nómina de aguinaldo, al 31 de diciembre del año. Si el empleado cumple años entre la fecha de pago y el 31 de diciembre, ya recibe los días del nuevo escalón. * En un finiquito, a la fecha de baja. Las vacaciones de un renglón se otorgan al cumplir el año que cierra ese periodo: al cumplir su primer año, el empleado recibe los días del renglón de 0 años, y al cumplir su sexto año, los del renglón de 5 a 9. El factor de integración, en cambio, toma esos días desde que empieza el periodo, así que un empleado con 0 años cumplidos ya integra los días del renglón de 0 años. Si un empleado tiene capturados sus propios días de aguinaldo en su ajuste de SBC, esos días se toman en lugar de la tabla para el factor de integración. ## Factor de integración (SBC) [#factor-sbc] El factor sube con prestaciones más generosas: más días de aguinaldo, más vacaciones o una prima vacacional mayor dan un salario base de cotización más alto y, con él, cuotas del IMSS mayores. En el cálculo de cada empleado, el factor toma los días de aguinaldo y de vacaciones de su contrato para su año de antigüedad. ## Quién recibe prestaciones [#quien-recibe] Los trabajadores asimilados a salarios y los de honorarios se calculan sin prestaciones (sin aguinaldo, prima, vacaciones, vales ni fondo de ahorro), sin importar su contrato. ## Tipos de contrato del SAT [#tipos-de-contrato] | Clave | Tipo | | ----- | --------------------------------------------------------------- | | 01 | Contrato por tiempo indeterminado | | 02 | Contrato para obra determinada | | 03 | Contrato por tiempo determinado | | 04 | Contrato por temporada | | 05 | Contrato sujeto a prueba | | 06 | Contrato con capacitación inicial | | 07 | Modalidad de contratación por pago de hora laborada | | 08 | Modalidad de trabajo por comisión laboral | | 09 | Modalidades de contratación donde no existe relación de trabajo | | 10 | Jubilación, pensión, retiro | | 99 | Otro contrato | ## Conceptos extralegales [#conceptos-extralegales] Los conceptos extralegales del contrato son los que otorga a sus empleados: los empleados que lo tienen los reciben automáticamente en cada nómina. Los conceptos de ley, como el sueldo, el ISR o las cuotas del IMSS, se calculan a cada empleado sin importar el contrato y no aparecen en esta lista. Un importe capturado a un empleado se paga aunque el concepto no esté en su contrato. Para agregar un concepto, escribe su nombre o código en **Conceptos extralegales** y elígelo de la lista; la búsqueda ofrece todos los conceptos extralegales activos, sin importar las razones sociales del contrato. Cada concepto agregado aparece debajo con su código, nombre y tipo. Para retirarlo, haz clic en la **X** de su renglón o vuelve a elegirlo en la búsqueda. Si un concepto del contrato se desactivó o se eliminó, su renglón lo indica con **Inactivo** o **Concepto eliminado**; elimínalo del contrato para poder guardar. Cada concepto de fórmula, monto fijo o captura manual tiene una lista **Cálculo** en su renglón; los que calcula la plataforma o un crédito conservan su propio cálculo. Con **Predeterminado** el concepto conserva su propio cálculo, que la lista muestra entre paréntesis, por ejemplo **Predeterminado (Captura manual)**. Con **Personalizado** eliges el **Tipo de cálculo** que aplica solo a los empleados de este contrato: * **Captura manual**: el concepto no paga nada hasta que se captura su importe al empleado, salvo que sea un concepto de monto fijo con un importe asignado al empleado. * **Monto fijo por periodo**: escribes el importe que se paga en cada periodo. * **Fórmula propia**: escribes la fórmula con el mismo editor del concepto, que valida y muestra una vista previa mientras escribes. Al elegir **Personalizado**, el tipo de cálculo parte de la configuración del concepto; si el concepto tiene fórmula, la fórmula se copia para que la modifiques. Si dejas vacío el monto o la fórmula, no puedes guardar. Al regresar a **Predeterminado** se elimina el cálculo personalizado del concepto. Al calcular la nómina, el importe de cada concepto sale del primero de estos que exista: el importe capturado al empleado en el periodo (una captura en cero se ignora), el importe asignado al empleado cuando el cálculo o el propio concepto es de monto fijo, el cálculo **Personalizado** del contrato y, por último, el cálculo del propio concepto. Para crear conceptos extralegales propios, consulta [Conceptos de nómina](/docs/configuration/concepts). ## Duplicar un contrato [#duplicar] Si necesitas un contrato parecido a otro, en el menú de opciones del contrato haz clic en **Duplicar**. Se crea un contrato nuevo con el mismo nombre seguido de **(COPIA)**; si esa copia ya existe, se numera: **(COPIA 2)**, **(COPIA 3)**. Si el nombre no cabe en 80 caracteres, se recorta. La copia conserva el tipo de contrato, la descripción, las prestaciones y los conceptos extralegales activos con su cálculo personalizado. También conserva las razones sociales a las que tienes acceso. Siempre queda activa, aunque el contrato original esté inactivo. Después puedes modificarla con **Editar**; las modificaciones no afectan al contrato original. ## Desactivar o eliminar [#desactivar-o-eliminar] Desde el menú de opciones puedes **Desactivar**, **Activar** o **Eliminar** un contrato. **Solo sin empleados vinculados.** No puedes desactivar ni eliminar un contrato que tenga empleados asignados. Asígnales otro contrato antes de continuar. # Plantillas de documentos (/docs/configuration/document-templates) Las plantillas de documentos definen el texto de las cartas de finiquito, liquidación y renuncia. Cada razón social tiene su propia redacción: lo que cambies en una no afecta a las demás. Se administran en **Configuración → Nómina → Plantillas de documentos**. ## Las tres cartas [#cartas] * **Carta finiquito**: el empleado confirma que recibió su finiquito. * **Carta de liquidación**: lo mismo, cuando el pago incluye indemnización. * **Carta de renuncia**: la manifestación voluntaria del empleado de terminar la relación laboral. Arriba de la carta elige la **Razón social** y el **Documento**. Cada razón social nueva empieza con el texto original de las tres cartas. Las cartas se descargan en PDF desde los finiquitos y desde la página del empleado; la guía [Finiquito y liquidación](/docs/terminations#cartas) explica dónde y qué carta ofrece cada escenario. ## Editar el texto [#editar] Necesitas el permiso para administrar la configuración de nómina. Sin él puedes ver las cartas, pero no el botón **Editar**. 1. Haz clic en **Editar**. 2. Escribe en los párrafos editables: **Apertura**, **Declaración** y **Cierre**. La carta de renuncia solo tiene **Apertura** y **Cierre**. 3. Haz clic en **Guardar**: la plataforma vuelve a la vista previa. Para salir sin guardar, haz clic en **Cancelar**. El título, la barra del importe, los datos generales, la tabla de conceptos y las firmas no se editan: aparecen marcados como **Fijo**. La tabla de conceptos lista cada percepción y deducción con su clave y su nombre, como P002 - Aguinaldo. Los **datos generales** se acomodan en dos columnas, cada dato en la forma Etiqueta: valor, por ejemplo Puesto: Analista. El **Horario** muestra los días y las horas de trabajo del empleado, por ejemplo lunes a viernes de 09:00 a 18:00. **Si tienes cambios sin guardar y cambias de razón social, de documento o abres otra pantalla desde el menú**, la plataforma te pide confirmar antes de descartarlos. Si cierras o recargas la pestaña, el navegador te avisa. Los botones de atrás y adelante del navegador no piden confirmación, así que guarda antes de usarlos. Con cambios sin guardar, **Descartar** los deshace y vuelve al último texto guardado. ### Variables [#variables] Las variables se llenan con los datos de cada empleado, como en la **Vista previa**. Hay dos formas de usarlas: * Desde el panel **Variables**, a la derecha: haz clic en una para insertarla donde está el cursor, o arrástrala al párrafo. * Escribiéndolas con su código en español entre dobles llaves, por ejemplo `{{nombre_empleado}}`. Hay variables del empleado (`{{nombre_empleado}}`, `{{numero_empleado}}`, `{{rfc_empleado}}`), de su empleo (`{{puesto}}`, `{{salario_diario}}`, `{{horario}}`), de fechas (`{{fecha_ingreso}}`, `{{fecha_baja}}`, `{{antiguedad}}`), del finiquito (`{{importe_neto}}`, `{{importe_neto_letra}}`) y de la empresa (`{{razon_social}}`, `{{ciudad}}`, `{{ciudad_y_fecha}}`). `{{numero_empleado}}` es el número que asigna la plataforma. Si tu organización tiene activado el [número interno](/docs/configuration#general), también aparece `{{numero_interno}}`, el número propio de tu empresa; queda vacío para un empleado que no lo tiene capturado. Si después desactivas el número interno, deja de aparecer en el panel, pero las cartas que ya lo utilizan se siguen guardando y llenando. Las del finiquito no existen en la carta de renuncia. Un código desconocido, o una variable que no aplica a esa carta, impide guardar y el aviso lo nombra. **La jornada del empleado.** Cinco variables del empleo describen su jornada, tomada del horario que tiene a la fecha de baja: su horario asignado, el paso de su rotación vigente ese día o, si no tiene ninguno, el [horario predeterminado](/docs/attendance/schedules) de la organización. Están en todas las cartas: | Variable | Código | Ejemplo | | ---------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------- | | **Días laborales** | `{{dias_laborales}}` | lunes a viernes | | **Días de descanso** | `{{dias_descanso}}` | sábado y domingo | | **Horario laboral** | `{{horario_laboral}}` | 09:00 a 18:00 | | **Jornada (días y horario)** | `{{jornada}}` | lunes a viernes de 09:00 a 18:00 | | **Declaración de jornada** | `{{declaracion_jornada}}` | Mi jornada laboral fue de lunes a viernes de 09:00 a 18:00, descansando los días sábado y domingo. | Tres o más días seguidos se escriben como un rango. Si el horario cambia según el día, **Horario laboral** y **Jornada (días y horario)** lo agrupan, por ejemplo lunes a jueves de 09:00 a 18:00 y viernes de 09:00 a 14:00. `{{horario}}` sigue siendo el nombre del horario, no sus días ni sus horas. Si el empleado no tiene horario, estas variables quedan vacías, y **Declaración de jornada** desaparece completa de la carta en vez de dejar espacios en blanco. **La carta de renuncia indica la jornada.** El texto original de la carta de renuncia incluye el puesto y el salario diario del empleado, termina su primer párrafo con `{{declaracion_jornada}}`, la jornada y los días de descanso del empleado, y el segundo indica que no se le adeuda salario, días festivos, vacaciones ni prima vacacional. Las cartas de renuncia que no habías personalizado ya traen este texto; las personalizadas conservan el tuyo, y **Restaurar** las devuelve al texto original. ### Cartas de más de una hoja [#varias-hojas] La carta se imprime en hojas tamaño carta. Si el texto ya no cabe en una, sigue en la siguiente, igual que en un procesador de textos: tanto al editar como en la **Vista previa** ves cada hoja tal como se imprime, con márgenes fijos de 1.27 cm (media pulgada) por lado. Los párrafos y la tabla de conceptos pueden continuar en la hoja siguiente (la tabla repite sus encabezados); el título, los datos generales y las firmas nunca se parten. Desde la segunda hoja se repite el título, y cuando la carta tiene más de una hoja cada una lleva su número, Página 1 de 2. El PDF que se descarga se parte en hojas exactamente donde lo muestra la **Vista previa**, con los mismos márgenes y la misma letra. **Guardar** se oculta si dejas un párrafo vacío o si un párrafo pasa de 3,000 caracteres: todos los párrafos de la carta deben tener texto. ## Vista previa [#vista-previa] Sin editar, la plataforma muestra la **Vista previa** de la carta. En **Empleado** elige el **Empleado de muestra** o un escenario de finiquito real de la razón social (escribe el nombre del empleado o la referencia para buscar entre todos los escenarios), para revisar cómo quedan los datos antes de entregarla. Para ver escenarios reales necesitas el permiso para administrar la configuración de nómina y el permiso para ver sueldos; sin ambos solo aparece el **Empleado de muestra**. **La lista solo muestra escenarios que imprimen esa carta.** Con la **Carta de liquidación** aparecen los escenarios que pagan indemnización; con la **Carta finiquito**, los que no; con la **Carta de renuncia**, solo los de **Renuncia voluntaria**. Los escenarios cancelados nunca aparecen, y la lista muestra los 50 más recientes que coinciden con tu búsqueda. Si cambias de **Documento** y el escenario elegido no imprime la nueva carta, la vista previa regresa al **Empleado de muestra**. ## Restaurar el texto original [#restaurar] Cuando una carta ya fue personalizada, la barra inferior ofrece **Restaurar**. Pide confirmación y devuelve la redacción que trae la plataforma para esa carta y esa razón social. ## Ciudad y fecha [#ciudad-y-fecha] La **Ciudad** de la línea de ciudad y fecha se captura en la [razón social](/docs/configuration/legal-entities). # Registros patronales (/docs/configuration/employer-registrations) El registro patronal del IMSS determina las cuotas obrero-patronales de los empleados que cotizan. Cada registro pertenece a una razón social, y una razón social puede tener varios, por ejemplo uno por estado o por sucursal. Se administran en **Configuración → Organización → Registros patronales**. ## Agregar un registro patronal [#agregar] 1. Haz clic en el botón para agregar, arriba a la derecha de la tabla. 2. Elige la **Razón social** a la que pertenece. 3. Captura el **Número de registro** tal como lo asignó el IMSS. 4. Si quieres, escribe una **Etiqueta** para reconocerlo, por ejemplo la sucursal. 5. Selecciona la **Clase de riesgo IMSS**, de Clase I a Clase V. 6. Escribe la **Prima de riesgo** y la fecha desde la que está **Vigente**. 7. Deja encendido **Activo** y haz clic en **Crear registro**. ![Formulario para agregar un registro patronal](/images/guides/getting-started/employer-registration-form.webp) **La prima se captura en porcentaje.** Escríbela tal como la publica el IMSS, con hasta cinco decimales: una prima de 0.54355 % se escribe **0.54355**. La plataforma no acepta valores mayores a 100 %. La tabla muestra la prima junto con la clase de riesgo, la fecha de vigencia y cuántos empleados usan el registro. ![Tabla de registros patronales con la prima en porcentaje](/images/guides/configuration/employer-registrations-table.webp) Dentro de una misma razón social no puede haber dos registros con el mismo número. ## Actualizar la prima de riesgo [#actualizar-la-prima] La prima de riesgo cambia cada año con la determinación que presentas al IMSS. Para registrarla: 1. Abre el menú de opciones del registro y elige **Editar**. 2. Cambia la **Clase de riesgo IMSS** o la **Prima de riesgo**. 3. Indica en **Vigente desde** la fecha en que empieza a aplicar la nueva prima. 4. Guarda los cambios. Cada cambio de clase o prima abre una nueva vigencia: la plataforma conserva el historial de las primas anteriores en lugar de sobrescribirlas. La razón social de un registro no se puede cambiar después de crearlo: al editarlo se muestra como texto, con una nota que lo explica. Si te equivocaste de razón social, crea el registro correcto. ## Desactivar o eliminar [#desactivar-o-eliminar] Desde el menú de opciones de la fila puedes **Desactivar**, **Activar** o **Eliminar** el registro. **Solo sin empleados vinculados.** No puedes desactivar ni eliminar un registro patronal que tenga empleados, incluidos los dados de baja. Cambia a esos empleados de registro patronal antes de continuar. # Catálogo de asistencias (/docs/configuration/incidence-types) El catálogo de asistencias lista los tipos de incidencia que puedes registrar a un empleado: faltas, vacaciones, permisos, incapacidades, retardos, días trabajados en descanso y horas extra. Está en **Configuración → Incidencias → Catálogo de asistencias**. **Es un catálogo fijo.** Lo administra la plataforma y es de solo lectura: no puedes agregar, editar ni eliminar tipos. Las incidencias se registran a cada empleado desde **Incidencias → Asistencia**. ![Catálogo de asistencias con su unidad, mapeo a nómina y si se pagan](/images/guides/configuration/incidence-types-catalog.webp) ## Columnas [#columnas] * **Nombre**: el tipo de incidencia. * **Unidad**: cómo se captura. **Días** para ausencias y vacaciones, **Ocurrencias** para eventos que se cuentan uno por uno, **Horas** para horas extra. * **Mapeo a nómina**: el dato de la nómina al que se suma la incidencia. * **Pagada**: si esos días u horas se pagan en la nómina (**Sí**) o no (**No**). ## Tipos de incidencia [#tipos] | Nombre | Unidad | Pagada | | ---------------------------------- | ----------- | ------ | | Vacaciones pagadas | Días | Sí | | Vacaciones no pagadas | Días | No | | Falta injustificada | Días | No | | Falta justificada | Días | No | | Permiso con goce de sueldo | Días | Sí | | Permiso sin goce de sueldo | Días | No | | Retardo | Ocurrencias | Sí | | Incapacidad por enfermedad general | Días | No | | Incapacidad por riesgo de trabajo | Días | No | | Incapacidad por maternidad | Días | No | | Domingo trabajado | Ocurrencias | Sí | | Día festivo trabajado | Ocurrencias | Sí | | Descanso semanal trabajado | Ocurrencias | Sí | | Horas extra dobles | Horas | Sí | | Horas extra triples | Horas | Sí | Varios tipos pueden sumarse al mismo dato de la nómina. Por ejemplo, las vacaciones no pagadas y los permisos sin goce de sueldo se suman como **Ausencias sin goce**. Los importes que no son de asistencia, como bonos o comisiones, no están en este catálogo: se capturan como conceptos de nómina desde la pestaña **Conceptos** de **Incidencias → Asistencia**. Consulta [Conceptos de nómina](/docs/configuration/concepts). # Configuración (/docs/configuration) En **Configuración** defines todo lo que la nómina necesita antes de calcularse: las empresas que emiten los recibos, sus registros ante el IMSS, la estructura de la organización, los calendarios, las prestaciones, los contratos y los conceptos. También administras quién entra a la plataforma y qué conexiones externas tiene. Entra desde el menú lateral, en **Configuración**. El menú cambia a las opciones de configuración; para volver, haz clic en el botón con la flecha, arriba a la izquierda: lleva el nombre de la pantalla desde la que entraste y te regresa a ella (o al **Dashboard** si entraste directo). Si vas empezando, sigue la guía [Primeros pasos](/docs/getting-started): recorre en orden lo mínimo para calcular tu primera nómina. Las páginas de esta sección explican cada opción con más detalle. ## General [#general] La primera opción, **Configuración → Organización → General**, guarda los datos de la organización: * **Logotipo** y **Logotipo cuadrado**, de hasta 5 MB cada uno. * **Nombre** de la organización. * **URL**: la dirección con la que entras a tu organización. * **Zona horaria**: las checadas del reloj se convierten a esta zona para calcular su fecha local. * **Habilitar número interno**: agrega el campo de número interno al alta y edición de empleados, su columna en la lista, en la [edición masiva](/docs/employees/bulk-edit) y en las descargas de Excel y CSV, y lo muestra junto al número de empleado en los selectores de empleados, donde también puedes buscarlo, y ofrece la variable `{{numero_interno}}` en las [plantillas de documentos](/docs/configuration/document-templates#variables). Si lo desactivas, los valores se ocultan pero no se borran. Haz clic en **Guardar cambios** para aplicar lo que modifiques. ![Pantalla General con logotipo, nombre, URL y zona horaria](/images/guides/configuration/general.webp) ## Qué hay en cada grupo del menú [#grupos-del-menu] **Organización** * [Razones sociales](/docs/configuration/legal-entities): las empresas que emiten la nómina, con su identidad fiscal y su certificado de sello digital. * [Registros patronales](/docs/configuration/employer-registrations): los registros ante el IMSS, con su clase y prima de riesgo. * [Estructura organizacional](/docs/configuration/organization-structure): departamentos, sucursales, puestos, centros de costo, tipos de unidades y unidades organizacionales. **Nómina** * [Calendarios de nómina](/docs/configuration/calendars): la periodicidad de pago, la tabla de ISR y los periodos del año. * [Contratos](/docs/configuration/contracts): el tipo de contrato ante el SAT, sus prestaciones y los conceptos extralegales. * [Catálogos de plataforma](/docs/configuration/platform-catalogs): UMA, salario mínimo, tarifas de ISR, cuotas del IMSS y demás parámetros fiscales. * [Plantillas de documentos](/docs/configuration/document-templates): el texto de las cartas de finiquito, liquidación y renuncia, por razón social. * **Bitácora**: el registro de cambios a la configuración de nómina, explicado en [Catálogos de plataforma](/docs/configuration/platform-catalogs#bitacora-de-nomina). **Incidencias** * [Catálogo de asistencias](/docs/configuration/incidence-types): los tipos de incidencia de asistencia que reconoce la nómina. * [Conceptos](/docs/configuration/concepts): las percepciones, deducciones y conceptos informativos del recibo. **Asistencia** * **Horarios**, **Reloj checador** y **Códigos QR**: se explican en las guías de asistencia. **Capital Humano** * **Bajas** y **Campos personalizados**: se explican en las guías de empleados. **Usuarios y permisos** * [Usuarios y permisos](/docs/configuration/access): invitaciones, roles y permisos. **Plataforma** * **API keys** e **Integraciones**: las llaves para conectar servicios externos, en [Usuarios y permisos](/docs/configuration/access#api-keys). * **Desarrolladores**: abre la documentación técnica de la API. # Razones sociales (/docs/configuration/legal-entities) La razón social es la empresa que emite la nómina ante el SAT. Los empleados, los calendarios y los registros patronales pertenecen a una razón social, y una organización puede tener varias. Se administran en **Configuración → Organización → Razón social**. ## Agregar una razón social [#agregar] 1. Haz clic en el botón para agregar, arriba a la derecha de la tabla. 2. Escribe el **Nombre** con el que la reconoces dentro de la plataforma. Es el único dato obligatorio. 3. Captura los datos fiscales: **Razón social fiscal**, **RFC**, **Código postal** y **Régimen fiscal (SAT)**. Puedes dejarlos para después. 4. Si quieres, escribe la **Ciudad** de la razón social, de hasta 80 caracteres, por ejemplo Monterrey, N.L. Es opcional y no forma parte de la identidad fiscal: las cartas de finiquito, liquidación y renuncia la usan en la línea de ciudad y fecha. 5. Deja encendido **Activo** y haz clic en **Crear razón social**. Cada razón social nueva queda vinculada al **Contrato base (indeterminado)** y al catálogo de conceptos de nómina de la organización. Con la primera razón social se crean; con las siguientes, solo se vinculan. El contrato base se crea con las prestaciones mínimas de ley: aguinaldo de 15 días, prima vacacional de 25 % y vacaciones según la tabla de ley. ## Identidad fiscal [#identidad-fiscal] Para que la razón social esté lista como emisora de recibos necesita cinco datos: * **Razón social fiscal**, tal como aparece en la constancia de situación fiscal. * **RFC** válido. * **Código postal** del domicilio fiscal, de cinco dígitos. * **Régimen fiscal (SAT)**, por ejemplo 601, General de Ley Personas Morales. * **Certificado de sello digital (CSD)** cargado, vigente y del mismo RFC. La columna **Identidad fiscal** de la tabla muestra **Completa** cuando no falta nada. Si dice **Incompleta**, pasa el cursor sobre ella para ver qué dato falta. Un dato mal escrito, como un RFC con formato inválido, cuenta como faltante. La columna **Conceptos timbrables** muestra **Completos** cuando todos los conceptos de nómina que usa la razón social tienen su clave SAT. Si a alguno le falta, la columna lo señala y puedes asignarle la clave en **Configuración → Incidencias → Conceptos**. ![Tabla de razones sociales con la identidad fiscal completa y el CSD cargado](/images/guides/configuration/legal-entities-table.webp) Para corregir cualquier dato, o para agregar la **Ciudad**, abre el menú de opciones de la fila y elige **Editar**. ## Certificado de sello digital (CSD) [#csd] El CSD se sube por separado, desde el menú de opciones de la fila, en **Certificado de sello digital**. **Captura primero el RFC.** La plataforma no acepta el certificado si la razón social todavía no tiene RFC. 1. En **Certificado (.cer)**, elige el archivo del certificado. 2. En **Llave privada (.key)**, elige el archivo de la llave. 3. Escribe la **Contraseña de la llave privada**. 4. Haz clic en **Subir CSD**. ![Diálogo para subir el certificado de sello digital](/images/guides/configuration/legal-entity-csd-dialog.webp) Antes de guardarlo, la plataforma revisa que el certificado sea un CSD válido del SAT, que su RFC coincida con el de la razón social, que esté vigente, que la contraseña abra la llave y que la llave corresponda al certificado. Si algo no cuadra, un aviso te dice qué revisar. La llave privada y la contraseña se guardan cifradas y no se vuelven a mostrar. Después de guardarlo, el CSD se carga al proveedor de timbrado (PAC). ### Estatus del CSD [#estatus-del-csd] La columna **CSD** de la tabla muestra el estado del certificado: * **Cargando al PAC**: la carga está en curso. * **Cargado al PAC**: listo. * **Error al cargar al PAC**: el certificado se guardó pero la carga falló. * **Vence pronto**: faltan 30 días o menos para que venza. * **Vencido**: ya no está vigente. Pasa el cursor sobre la etiqueta para ver la fecha de vencimiento. Al abrir **Certificado de sello digital** con un CSD ya cargado, verás el RFC del certificado, su número, su vigencia y el **Estatus en el PAC**. ![Detalle del CSD con su vigencia y estatus en el PAC](/images/guides/configuration/legal-entity-csd-status.webp) Desde ese diálogo puedes: * **Renovar CSD**: sube el nuevo certificado cuando el SAT te emita uno. Hazlo antes de que venza el actual; sin un CSD vigente la identidad fiscal queda incompleta. * **Reintentar carga**: aparece en lugar de **Renovar CSD** cuando la carga al PAC falló. * **Eliminar CSD**: quita el certificado de la razón social. Mientras la carga al PAC está en curso no puedes renovar ni eliminar el certificado. ## Desactivar o eliminar [#desactivar-o-eliminar] Desde el menú de opciones de la fila: * **Desactivar** oculta la razón social sin borrarla. Puedes volver a activarla con **Activar**. * **Eliminar** la quita de formularios y listados. Los registros patronales asociados se conservan. **Solo sin empleados vinculados.** No puedes desactivar ni eliminar una razón social que tenga empleados, incluidos los dados de baja. El diálogo te dice cuántos hay; reasígnalos a otra razón social antes de continuar. # Estructura organizacional (/docs/configuration/organization-structure) La estructura organizacional describe cómo está armada la empresa. Ninguno de estos datos es obligatorio para calcular la nómina, pero sirven para ubicar a cada empleado y para filtrar y agrupar la información. Se asignan a cada empleado en la sección **Organización** de su formulario. Todo se administra en **Configuración → Organización**: * **Departamentos**, **Sucursales** y **Centros de costo** son árboles: cada elemento puede tener otros debajo. * **Puestos** es una lista sin niveles. * **Plazas** son los lugares autorizados de cada puesto por razón social. * **Tipos de unidades** y **Unidades organizacionales** te permiten crear estructuras propias, como regiones o zonas. ## Departamentos, sucursales y centros de costo [#arboles] Las tres pantallas funcionan igual. Cada fila es un nodo del árbol; despliégala para ver los que dependen de ella. A la derecha verás cuántos subnodos y cuántos empleados tiene. ![Árbol de departamentos con Administración y Operaciones](/images/guides/configuration/departments-tree.webp) Para agregar uno: 1. Haz clic en el botón para agregar, arriba a la derecha. 2. Escribe el **Nombre**. El **Código** y la **Descripción** son opcionales. 3. En el campo del padre, elige de qué nodo depende. Déjalo en **Sin nodo padre (raíz)** para crearlo en el primer nivel. 4. Indica el **Orden** en que aparece entre sus hermanos y deja encendido **Activo**. 5. Haz clic en el botón para crear. ![Formulario para agregar un departamento](/images/guides/configuration/department-form.webp) Para crear uno directamente debajo de otro, abre el menú de opciones del nodo y elige **Agregar subdepartamento**, **Agregar subsucursal** o **Agregar subcentro**, según la pantalla. Desde ese mismo menú puedes **Editar**, **Desactivar** o **Eliminar**. ![Árbol de sucursales](/images/guides/configuration/branches-tree.webp) ![Árbol de centros de costo](/images/guides/configuration/cost-centers-tree.webp) ## Puestos [#puestos] Los puestos son una lista. Además de **Nombre**, **Código**, **Descripción**, **Orden** y **Activo**, cada puesto puede llevar una banda salarial con **Salario mensual mínimo** y **Salario mensual máximo**. Ambos datos son opcionales. La clase de riesgo IMSS no se captura en el puesto sino en el registro patronal. ![Formulario para agregar un puesto](/images/guides/configuration/job-position-form.webp) La tabla muestra la banda salarial y cuántos empleados tienen cada puesto. ![Tabla de puestos](/images/guides/configuration/job-positions-table.webp) ## Plazas [#plazas] Una plaza es un lugar autorizado de un puesto dentro de una razón social. Cada plaza tiene a lo más un ocupante; si un puesto tiene cinco lugares autorizados, crea cinco plazas. Para crear plazas, ve a **Configuración → Organización → Plazas** y haz clic en **+ Plazas**: 1. Indica la **Cantidad de plazas**. Todas se crean con los mismos datos. 2. Elige la **Razón social** y el **Puesto**. Departamento, sucursal, centro de costos, registro patronal y **Reporta a** son opcionales. 3. Captura el **Salario diario autorizado** si lo necesitas. Si el puesto tiene banda salarial, el salario diario por 30 debe quedar dentro de ella. 4. Elige el **Tipo de plaza**: **Indeterminada**, o **Temporal** con su **Término de la plaza**. 5. Indica el **Inicio de vigencia** y, si aplica, el **Fin de vigencia**. Marca **Sindicalizada** cuando corresponda. El **Código** es opcional y solo aparece cuando creas una plaza a la vez, porque no puede repetirse. La tabla muestra el puesto, la razón social, el ocupante, el salario autorizado, el estatus y los días que la plaza lleva vacante. Haz clic en una fila para ver su resumen, o en el código para abrir su detalle con el historial de ocupantes. El estatus de una plaza es **Vacante**, **Ocupada**, **Congelada** o **Cancelada**: * **Congelar** deja la plaza autorizada pero sin poder ocuparse; **Reactivar** la vuelve a dejar vacante. Solo se congela una plaza vacante. * **Cancelar plaza** la deja de autorizar y conserva su historial. No se puede deshacer, y una plaza ocupada no se puede cancelar. Tampoco se puede cancelar mientras le reporten plazas de razones sociales a las que no tienes acceso. La razón social de una plaza no se puede modificar después de crearla, y el puesto solo se puede modificar mientras la plaza no esté ocupada. ## Tipos de unidades y unidades organizacionales [#unidades-organizacionales] Si tu empresa se organiza de una forma que no cabe en departamentos o sucursales, por ejemplo por región o por línea de servicio, créala con unidades organizacionales. Primero define los tipos en **Configuración → Organización → Tipos de unidades**. Cada tipo lleva **Nombre**, **Clave**, **Color**, **Ícono** y **Orden**. No puede haber dos tipos con la misma clave. ![Pantalla de tipos de unidades sin registros](/images/guides/configuration/unit-types-empty.webp) Después crea las unidades en **Configuración → Organización → Unidades organizacionales**. Cada unidad lleva **Tipo de unidad**, **Nombre**, **Código**, **Unidad padre**, **Orden** y **Activo**. La pantalla no te deja crear unidades mientras no exista al menos un tipo. **Una unidad solo puede depender de otra del mismo tipo.** Una región puede tener subregiones, pero no puede colgar de una línea de servicio. Tampoco puedes mover una unidad dentro de su propio subárbol. En el formulario de cada empleado, la sección **Organización** muestra un campo por cada tipo de unidad, para asignarle una unidad de cada tipo. ## Desactivar y eliminar [#desactivar-y-eliminar] Las reglas cambian según el elemento: | Elemento | Al desactivar | Al eliminar | | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | Departamentos, sucursales, centros de costo | Se quita de todos los empleados que lo tenían asignado. | No se permite si tiene subnodos. Si no los tiene, se quita de los empleados y desaparece de formularios y listados. | | Puestos | No se permite mientras el puesto tenga plazas sin cancelar. Si no las tiene, se elimina de todos los empleados que lo tenían asignado. | No se permite mientras el puesto tenga plazas sin cancelar. Si no las tiene, se elimina de los empleados y desaparece de formularios y listados. | | Unidades organizacionales | No se permite si tiene subunidades o empleados asignados, en ella o en sus subunidades. | Misma regla que al desactivar. | | Tipos de unidades | No se permite mientras existan unidades de ese tipo. | Misma regla que al desactivar. | **Quitar un elemento de los empleados no se puede deshacer desde esta pantalla.** Antes de desactivar, el diálogo te dice cuántos empleados se verán afectados. Si vuelves a activar el elemento, tendrás que asignarlo de nuevo a cada empleado. Para eliminar un nodo con subnodos, elimina primero sus subnodos o cámbialos de padre desde **Editar**. # Catálogos de plataforma (/docs/configuration/platform-catalogs) Los catálogos de plataforma son los parámetros fiscales que usa el cálculo de la nómina: la UMA, el salario mínimo, las tarifas de ISR, las cuotas del IMSS y demás. La plataforma los mantiene actualizados conforme cambia la legislación, así que normalmente no tienes que hacer nada. Están en **Configuración → Nómina → Catálogos de plataforma**. ![Catálogos de plataforma agrupados por tema](/images/guides/configuration/platform-catalogs.webp) ## Qué catálogos hay [#catalogos] **Parámetros generales** * **UMA**: diaria, mensual y anual. * **Salario mínimo general**: el general y el de la Zona Libre de la Frontera Norte. * **Factor de días por semana/quincena/mes/año**: los días con los que un sueldo semanal, quincenal, mensual o anual se convierte a diario. El mes de este catálogo, siempre entre 28 y 31, solo sugiere los **Días del mes** al crear un calendario nuevo; cada calendario guarda los suyos (30 o 30.4) y cambiar el catálogo no modifica los calendarios existentes. * **Tope SBC (UMA)**: el tope del salario base de cotización, en veces la UMA. * **Exención horas extra (UMA/semana)**: el tope de exención de las horas extra por semana. * **Límites semanales de horas extra**: el máximo de horas extra por día y de días con horas extra por semana. **ISR e impuesto sobre nómina** * **Tarifa ISR**: una tarifa por periodicidad. * **Tarifa ISR anual**: la tarifa del año, la que utiliza el ajuste anual de ISR. * **Subsidio para el empleo**. * **Impuesto sobre nómina por estado**: la tasa de cada entidad federativa, con la tarifa progresiva, las sobretasas y el monto exento de los estados que los tienen. **IMSS** * **Cuotas IMSS obrero** y **Cuotas IMSS patronal**, por ramo de aseguramiento. * **Cesantía y vejez patronal**: la tabla graduada por salario. **INFONAVIT** * **Tasa INFONAVIT patronal**. * **UMI (Unidad Mixta Infonavit)**: la unidad en la que se actualizan los créditos INFONAVIT otorgados en veces salario mínimo. Cada fila muestra el valor vigente y dos etiquetas: **De ley** cuando el valor lo fija la legislación, y quién lo administra. ## Versiones [#versiones] Haz clic en un catálogo para ver su detalle: el **Valor vigente** y el **Historial de versiones**, con la fecha desde la que aplica cada versión. ![Detalle del catálogo UMA con su valor vigente y el historial de versiones](/images/guides/configuration/platform-catalog-detail.webp) Cuando la plataforma publica una versión nueva, por ejemplo la UMA de cada febrero, se aplica sola a tu organización a partir de su fecha de vigencia. Las nóminas de fechas anteriores siguen usando la versión que les corresponde. La **UMA** trae sus valores desde 2016, con una versión por año, para que un alta, un SBC o un finiquito con fecha pasada usen la UMA de su fecha. Los demás catálogos empiezan el 1 de enero de 2026. ## Adelantar una versión [#adelantar-version] Cuando la plataforma ya publicó una versión que todavía no entra en vigor, puedes hacer que una o varias razones sociales la utilicen desde antes. Los valores los sigue administrando la plataforma: no tienes que capturarlos. 1. Abre el catálogo y haz clic en **Adelantar versión**. El botón solo aparece cuando hay una versión publicada con fecha futura. 2. En **Versión**, elige la versión que quieres adelantar. 3. En **Razones sociales**, elige una o varias. Una razón social que autoadministra el catálogo no puede adelantarlo. 4. En **Aplicar desde**, elige el día desde el que la utilizarán. Puede ser hoy o cualquier día antes de su entrada en vigor, siempre que sea posterior al inicio de la versión vigente. 5. Escribe el **Motivo**. Es obligatorio en las tablas federales. 6. Haz clic en **Adelantar**. Cada razón social aparece en **Adopciones anticipadas** con la fecha desde la que utiliza la versión y la fecha en que entra en vigor para todos. Las nóminas con fecha anterior a la que elegiste siguen utilizando la versión que les corresponde, y las nóminas cerradas conservan sus valores. Para dejar de adelantarla, haz clic en el botón de cancelar de esa razón social y confirma con **Sí, cancelar**. Solo se puede cancelar, o cambiar su fecha, antes de que llegue el día que elegiste; desde ese día la razón social se queda con la versión adelantada. Si tu organización autoadministra el catálogo, **Adelantar versión** no aparece, y mientras sus valores propios se aplican, las adopciones de **Adopciones anticipadas** no se aplican. ## Historial de cambios [#historial-de-cambios] El botón **Historial de cambios**, arriba de la lista, muestra todas las versiones publicadas por la plataforma: **Vigente desde**, **Catálogo**, **Versión**, **Tipo** y **Motivo**. Puedes buscar por catálogo o motivo y filtrar la lista. ![Historial de cambios de los catálogos de plataforma](/images/guides/configuration/platform-catalogs-history.webp) ## Autoadministrar un parámetro [#autoadministrar] Si tu organización necesita un valor distinto al oficial, abre el catálogo y haz clic en **Autoadministrar**. **Autoadministrar te hace responsable del valor.** Tu organización asume mantenerlo correcto y actualizado, y la plataforma deja de aplicarle actualizaciones automáticas. Después de confirmar puedes editar sus valores. Cuando la plataforma publique una versión nueva de un parámetro que autoadministras, se te pedirá revisarla: puedes conservar tu valor o adoptar la nueva referencia publicada. El parámetro sigue autoadministrado en ambos casos. Cada vez que la plataforma publica una versión nueva de un parámetro, los miembros de tu organización con acceso a los catálogos de plataforma reciben un correo con la fecha en que entra en vigor y la publicación oficial de la que viene. Si autoadministras ese parámetro, el correo te pide revisarlo; si no lo has revisado, te lo recordamos a los 7 y a los 14 días de la publicación. Para regresar al valor oficial, usa el botón **Volver a versión** de la plataforma, en el mismo lugar donde estaba **Autoadministrar**. Se pierden las personalizaciones de ese parámetro y vuelve a actualizarse solo. ### Tablas federales [#tablas-federales] La **Tarifa ISR**, la **Tarifa ISR anual**, las **Cuotas IMSS obrero**, las **Cuotas IMSS patronal** y la tabla de **Cesantía y vejez patronal** llevan la etiqueta **Tabla federal** y tienen un paso más: 1. Al autoadministrarla, escribe el motivo por el que tu organización usa una tabla distinta a la oficial. 2. Marca la casilla en la que aceptas que el SAT no validará tus retenciones contra esa tabla y que es bajo tu responsabilidad fiscal. 3. La tabla queda marcada como **No oficial · revisión pendiente**. 4. Otro administrador abre el catálogo, hace clic en **Aprobar (segundo administrador)** y confirma con **Aprobar bajo responsabilidad**. **Se necesita un segundo administrador.** Quien autoadministra la tabla no puede aprobarla. Hasta que otro administrador la apruebe, las corridas de nómina siguen usando la tabla oficial. Cada vez que cambies los valores de una tabla federal, vuelve a quedar pendiente de aprobación. La aprobación queda registrada en la bitácora. ## Impuesto sobre nómina por estado [#isn-por-estado] Al abrir **Impuesto sobre nómina por estado** verás la configuración de las 32 entidades: su **Tasa**, la **Referencia de plataforma** (ambas vigentes hoy), cuántos **Conceptos en la base** tiene y su **Origen**, que es **Predeterminado** o **Configurado**. ![Configuración del impuesto sobre nómina por estado](/images/guides/configuration/isn-states.webp) Desde el menú de opciones de un estado: * **Editar**: cambia la **Tasa de ISN (%)**, elige los conceptos que forman la base del impuesto en ese estado y agrega **Notas**, por ejemplo el artículo de la ley de hacienda o la fecha de vigencia. * **Restablecer**: regresa el estado a los valores sugeridos por la plataforma, incluida la tarifa progresiva. **Estados con tarifa o sobretasa.** Sinaloa muestra **Tarifa progresiva** en lugar de una tasa. Querétaro muestra **3.00% con monto exento**. Los estados con sobretasa muestran su tasa y la sobretasa, por ejemplo **3.00% con sobretasa del 33%** en Campeche. Al editar Sinaloa, el campo de tasa se abre vacío y es obligatorio capturarla. Al guardar una tasa propia, esa tasa reemplaza la tasa o la tarifa progresiva publicada; la sobretasa y el monto exento del estado se siguen aplicando sobre ella. Al restablecer, regresa la tasa o la tarifa publicada. El ISN de cada empleado se calcula con la configuración del estado que tiene en **Entidad del centro de trabajo**. Las tarifas y sobretasas publicadas aplican a todos los periodos de 2026. Una nómina abierta las toma al volver a calcularse; una nómina cerrada conserva los valores con que se calculó. ## Bitácora de nómina [#bitacora-de-nomina] En **Configuración → Nómina → Bitácora** se registran los cambios a la configuración de nómina: calendarios, contratos, conceptos, catálogos, ISN por estado, sueldos, horarios y reloj checador, entre otros. Muestra los 300 eventos más recientes. Cada renglón indica la **Fecha**, el **Tipo** de registro, la **Acción** (por ejemplo **Creación**, **Edición**, **Desactivación** o **Autoadministración**), el **Detalle** con los campos que cambiaron y su valor anterior y nuevo, y el **Responsable**. Puedes buscar en el detalle y filtrar por tipo, acción o responsable. ![Bitácora de nómina con la creación de un calendario](/images/guides/configuration/payroll-audit.webp) # Dashboard (/docs/dashboard) El **Dashboard** es la primera pantalla al entrar a la organización. Resume la última nómina cerrada, el personal activo, las incidencias y los próximos eventos del calendario, y cada tarjeta lleva al módulo donde está el detalle. ## Organización nueva [#organizacion-nueva] Una organización nueva no entra al Dashboard hasta terminar la [configuración inicial](/docs/getting-started#configuracion-inicial). Después, los pasos que faltan para operar la nómina aparecen en la tarjeta **Primeros pasos** del menú lateral. ## Las tarjetas [#tarjetas] ![Dashboard con los indicadores de la nómina y del personal](/images/guides/dashboard/dashboard.webp) | Tarjeta | Qué muestra | Lleva a | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | | **Empleados activos** | Cuántos empleados están activos y cómo se compara con el periodo anterior. | La lista de empleados. | | **Nómina del periodo** | El neto de la última nómina cerrada y su estado de pago: **Sin dispersar**, **Lote sin enviar** o **Enviada al banco**, con cuántos recibos quedan sin dispersar. Si todavía no hay ninguna, lo indica. | Esa corrida. | | **Costo IMSS patronal** | Las cuotas patronales de esa misma nómina. | Esa corrida. | | **Incidencias** | Las faltas, retardos e incapacidades registradas. | La pestaña Incidencias de Asistencia. | | **Costo de nómina mensual** | Los últimos seis meses, desglosados en neto pagado, IMSS patronal, ISR retenido y otras cargas patronales. | El cálculo de nómina. | | **Distribución por departamento** | El porcentaje de empleados en cada área. | La lista de empleados. | | **Próximos eventos de nómina** | Los cierres y pagos del calendario y los créditos de empleados que están por terminar. | El calendario o el empleado. | | **Preparación de nómina** | Los empleados activos a los que les falta algún dato para pagarse, y qué les falta. | Cada empleado. | ## Qué ve cada usuario [#permisos] Cada tarjeta aparece solo si el rol del usuario permite ver su información: las de la nómina requieren las corridas de nómina, las del personal requieren empleados, la de incidencias requiere incidencias, y el costo mensual requiere además ver los sueldos. Si el rol no permite ver ninguna, el Dashboard lo indica y sugiere pedir acceso al administrador. La guía [Usuarios y permisos](/docs/configuration/access) explica cómo se configuran los roles. # Portal de colaborador (/docs/employee-portal) El portal de colaborador es el espacio donde cada empleado consulta su propia información, su asistencia, sus incidencias y sus recibos, y solicita tiempo libre, la corrección de una checada que falta o la justificación de un retardo. Los supervisores ven además a las personas que les reportan y deciden sus solicitudes. Los demás datos se capturan y corrigen desde la plataforma. ## Quién tiene acceso [#acceso] Un empleado puede entrar al portal cuando se cumplen tres condiciones: * Tiene marcada la casilla **Acceso al portal de colaborador**, al pie de su formulario. * Tiene capturado, en la sección Registro de su formulario, el dato de su **Método de acceso**: el **Correo electrónico** si entra con correo, o el **Teléfono** si entra con teléfono. * Tiene una relación laboral abierta. Un empleado dado de baja pierde el acceso; si se reactiva, lo recupera. La casilla viene marcada en cada alta nueva. Si no quieres que un empleado entre al portal, desmárcala en su formulario. ![Formulario del empleado con la casilla Acceso al portal de colaborador marcada](/images/guides/employees/employee-profile.webp) **Método de acceso.** Al marcar la casilla eliges cómo entra el empleado en **Método de acceso**: * **Correo**: entra con el correo de su registro. * **Teléfono**: entra con el teléfono de su registro y recibe un código por WhatsApp. Sirve para quienes no tienen correo, por ejemplo el personal que solo checa con la app. Dos empleados de la organización no pueden entrar con el mismo teléfono. **El dato del método es la llave del portal.** Si cambias el correo, el teléfono o el método de acceso, la cuenta anterior deja de tener acceso y el empleado entra con el dato nuevo. ## Cómo inicia sesión el colaborador [#inicio-de-sesion] El portal no usa contraseñas. En la página **Portal de colaborador** de tu organización, el colaborador elige **Correo** o **Teléfono**, según su método de acceso. **Con correo**, entra con un enlace que recibe por correo: 1. Escribe el **Correo electrónico** que tiene capturado en su registro. 2. Hace clic en **Enviar enlace de acceso**. 3. Abre el correo "Enlace de acceso al portal de colaborador" y hace clic en el enlace. **Con teléfono**, entra con un código de 6 dígitos que recibe por WhatsApp: 1. Escribe el **Teléfono** que tiene capturado en su registro. 2. Hace clic en **Enviar código**. 3. Escribe el código que recibió y hace clic en **Verificar código**. ![Página de inicio de sesión del portal de colaborador](/images/guides/employee-portal/login.webp) El enlace vence a los 15 minutos y el código a los 5; si vence, basta con pedir otro desde la misma página. Si el correo o el teléfono no tiene acceso al portal, la página lo indica y no envía nada. En la app, el colaborador entra igual: elige **Correo** o **Teléfono** y escribe el código que recibe por correo o por WhatsApp. Si el dato no tiene acceso, la app muestra "Credenciales incorrectas" y sugiere probar con el otro método. ## La pantalla de inicio [#inicio] Al entrar, el colaborador ve un saludo con la fecha, la hora de su zona horaria y un mosaico con los módulos del portal. El buscador **Buscar módulo** filtra los mosaicos por nombre. ![Pantalla de inicio del portal con los módulos disponibles](/images/guides/employee-portal/home.webp) ## Módulos [#modulos] * **Resumen**: su próximo pago, su último recibo, su saldo de vacaciones, su asistencia de la semana y sus solicitudes pendientes. * **Mi perfil**: sus datos personales, de identificación, de empleo y bancarios. * **Nómina**: sus recibos de nómina timbrados, con descarga en PDF y XML. * **Asistencia**: sus checadas y lo que se registró en su asistencia, mes por mes. * **Tiempo libre**: su saldo de vacaciones y sus solicitudes de vacaciones y permisos. * **Incidencias**: las faltas, retardos, horas extra, bonos y deducciones que tiene registrados, mes por mes. * **Solicitudes**: todas las solicitudes que envía y, para supervisores, las que debe aprobar. * **Mi equipo**: solo para supervisores, las personas que les reportan, en lista o en organigrama. Las guías [Lo que ve el colaborador](/docs/employee-portal/worker) y [Lo que ve el supervisor](/docs/employee-portal/supervisor) describen cada módulo. # Lo que ve el supervisor (/docs/employee-portal/supervisor) Un supervisor ve en el portal lo mismo que cualquier colaborador y, además, a las personas que le reportan: su lista y organigrama, su asistencia, sus incidencias y su perfil. También decide sus solicitudes. ## Quién es supervisor [#quien-es-supervisor] En el portal, un colaborador es supervisor cuando al menos un empleado con relación laboral abierta lo tiene como **Supervisor inmediato**. Ese dato se captura en la sección Organización del formulario del empleado; la guía [Alta de empleados](/docs/employees/hiring#secciones) lo explica. El supervisor ve a su equipo hasta tres niveles: sus reportes directos, los reportes de ellos y un nivel más abajo. Mientras no tenga reportes, el colaborador no ve el mosaico **Mi equipo** ni las pestañas de equipo en Asistencia, Incidencias, Mi perfil y Solicitudes. ## Mi equipo [#mi-equipo] El módulo **Mi equipo** muestra a las personas que le reportan, en dos vistas. **Lista.** Una tarjeta por persona con su nombre, su puesto y su departamento. Los reportes directos llevan la etiqueta **Directo**. Usa la búsqueda y **Filtros** para acotar por supervisor, departamento o puesto, o para ver solo a los reportes directos. ![Módulo Mi equipo en la vista Lista con un reporte directo](/images/guides/employee-portal/my-team.webp) **Organigrama.** El árbol del equipo con el supervisor arriba y sus reportes debajo; cada persona muestra su puesto y cuántos reportes directos tiene. Con los botones de acercar y alejar ajustas la vista. ![Módulo Mi equipo en la vista Organigrama](/images/guides/employee-portal/my-team-org-chart.webp) ## Asistencia del equipo [#asistencia-del-equipo] En el módulo **Asistencia**, la pestaña **Mi equipo** muestra una fila por persona con los **Días** con checada, los **Retardos** y la **Última checada** del mes elegido. Las flechas junto al nombre del mes cambian el periodo, igual que en la asistencia propia. ![Pestaña Mi equipo del módulo Asistencia con la asistencia del mes de un colaborador](/images/guides/employee-portal/attendance-team.webp) Haz clic en una persona para ver su asistencia del mes día por día, con el mismo detalle que ve el propio colaborador. ## Perfil de mi equipo [#perfil-de-mi-equipo] En el módulo **Mi perfil**, la pestaña **Perfil de mi equipo** lista a las personas del equipo. Al hacer clic en una se abre su **Perfil del colaborador**, con sus datos personales, de identificación y de empleo. Los datos bancarios de su equipo no se muestran al supervisor. ## Incidencias del equipo [#incidencias-del-equipo] En el módulo **Incidencias**, la pestaña **Mi equipo** muestra en una tabla las incidencias de asistencia del mes de las personas del equipo. Los montos de nómina de cada persona no se muestran al supervisor. Si la organización lo permite, la pestaña muestra además **Capturar incidencia**, para registrar a sus reportes directos una **Falta injustificada**, un **Retardo**, **Horas extra dobles** o **triples**, un **Domingo trabajado**, un **Día festivo trabajado** o un **Descanso semanal trabajado**: 1. Haz clic en **Capturar incidencia**. 2. Elige a los **Empleados**, el tipo de incidencia y la **Fecha** o, con **Capturar un rango de fechas**, el periodo. Para horas y ocurrencias, indica la cantidad. 3. Haz clic en **Capturar**. Se pueden capturar fechas del mes en curso y del mes anterior, hasta hoy. Si la fecha cae en una nómina ya cerrada, la captura se rechaza y el ajuste lo hace Recursos Humanos. Si ya hay horas extra rechazadas del mismo tipo en esa fecha, sin checadas vinculadas, la captura también se rechaza: Recursos Humanos corrige el registro rechazado. La incidencia queda en el registro de asistencia de la plataforma con el origen **Portal de colaborador**. Las horas extra que captura el supervisor quedan autorizadas por él como jefe directo y se pagan en la nómina del periodo; la guía [Autorización de horas extra](/docs/attendance/incidences#autorizacion-horas-extra) lo explica. Sin ese permiso, la pestaña es de solo lectura. La guía [Incidencias y conceptos](/docs/attendance/incidences#supervisores) explica cómo activarlo. ## Solicitudes por aprobar [#solicitudes] En el módulo **Solicitudes**, la pestaña **Por aprobar** reúne en una sola tabla las solicitudes de tiempo libre, las correcciones de checada, las justificaciones de retardo y las [horas extra por autorizar](#horas-extra) de las personas que le reportan directamente, con el número de pendientes junto al nombre. La tabla muestra al **Colaborador**, el **Tipo**, la **Solicitud**, el **Estatus** y cuándo se solicitó. **Filtros** acota por colaborador, tipo y estatus. ![Pestaña Solicitudes con una corrección pendiente](/images/guides/employee-portal/supervisor-requests.webp) Desde el menú de cada solicitud: * **Ver detalle** muestra la solicitud. En una corrección, incluye **Cómo quedaría el día** con la checada agregada. * **Aprobar** aplica la solicitud. El **Comentario para el colaborador** es opcional. * **Rechazar** no cambia nada, y el comentario es obligatorio: el colaborador lo recibe como explicación. ![Diálogo para aprobar una corrección](/images/guides/employee-portal/approve-correction.webp) **Correcciones.** La checada aprobada aparece en la asistencia de la plataforma con el origen **Corrección aprobada**, y los retardos del día se vuelven a calcular. **Justificaciones de retardo.** Al aprobar, el retardo se elimina de la asistencia del colaborador y la plataforma no lo vuelve a registrar. Un retardo que ya se pagó en una nómina cerrada no se puede justificar; ese caso lo atiende Recursos Humanos. Si el día pertenece a un periodo de nómina ya cerrado, la plataforma pide confirmar el ajuste retroactivo antes de aprobar. **Recursos Humanos también puede resolverlas.** Las correcciones y las justificaciones las resuelve quien actúe primero: el supervisor desde el portal o Recursos Humanos desde la plataforma. Si Recursos Humanos ya la resolvió, la solicitud deja de aparecer como pendiente. Las solicitudes de tiempo libre siguen su cadena de aprobación: primero el supervisor y, si la organización lo pide, Recursos Humanos después. ## Horas extra por autorizar [#horas-extra] Si la organización tiene encendido **Autoriza el jefe directo**, las horas extra dobles y triples de sus reportes directos que capturó alguien más, por ejemplo Recursos Humanos o una integración, esperan su autorización. El supervisor las decide desde el portal web o desde la app del portal de colaborador. En los dos, **Solicitudes** las reúne en **Por aprobar** junto con las demás solicitudes del equipo, con el tipo **Horas extra**, y las cuentan entre las pendientes: en el portal web, la tarjeta **Por aprobar** del **Resumen** y el número junto a la pestaña; en la app, el menú y la sección **Mi equipo** del inicio. Cada una muestra las horas, el tipo, el día, las checadas ligadas y las notas de la captura; en el portal web, con **Ver detalle**. * **Aprobar** las autoriza. Si la organización también pide la autorización de Recursos Humanos, pasan a su revisión; si no, se pagan en la nómina del periodo. * **Rechazar** pide un **Comentario para el colaborador** (en la app, **Motivo**), y las horas extra rechazadas no se pagan. Si el día pertenece a un periodo de nómina ya cerrado, la plataforma pide confirmar el ajuste retroactivo antes de aprobar. Recursos Humanos también puede aprobarlas o rechazarlas por el supervisor desde la plataforma; si ya lo hizo, dejan de aparecer como pendientes. # Lo que ve el colaborador (/docs/employee-portal/worker) Todo colaborador con acceso al portal consulta su información en cada módulo, y desde **Solicitudes**, **Tiempo libre** o **Asistencia** pide tiempo libre, la corrección de una checada que falta o la justificación de un retardo. Los demás cambios los pide a Recursos Humanos. Desde cualquier módulo, **Volver al inicio** regresa a la pantalla de inicio. ## Asistencia [#asistencia] La pestaña **Mi asistencia** muestra el mes en curso. Con las flechas junto al nombre del mes se consultan los meses anteriores. ![Módulo Asistencia del portal con el resumen del mes y la lista de días](/images/guides/employee-portal/attendance.webp) Arriba aparecen tres tarjetas con el resumen del mes: * **Días con checada**: los días del mes con al menos un registro. * **Retardos**: los que resultan de comparar su horario con sus checadas. * **Incidencias**: las registradas por Recursos Humanos, como una falta. Debajo está la lista de días, del más reciente al más antiguo. Cada día muestra: * Las horas de **Entrada** y **Salida** y, si su horario incluye comida, la salida y el regreso de comida. Si falta alguna checada, el día lo indica. * El horario contra el que se midió el día, o **Día de descanso**, o **Sin horario asignado**. * Lo que quedó registrado ese día, como un retardo o una incidencia, en forma de etiquetas. Un festivo muestra **Festivo**, y un día de tiempo libre con checadas muestra **Checada en vacaciones**, **Checada en permiso**, **Checada en incapacidad**, **Festivo trabajado** o **Descanso trabajado**. **Un día sin checadas no siempre cuenta como falta.** Un día laborable ya pasado sin registros aparece como **Sin checadas**, es decir, incompleto. Solo cuenta como falta si Recursos Humanos la captura como incidencia o si la organización activó **Derivar faltas**; en ese caso, la falta se retira sola si después llega una checada de ese día. Los días de vacaciones, permiso, incapacidad, descanso o festivo nunca aparecen como **Sin checadas**. En la app, cada día muestra su estatus, por ejemplo **Completa**, **Incompleta**, **Sin checadas**, **Descanso**, **Vacaciones**, **Permiso**, **Incapacidad** o **Festivo**. **Falta** solo aparece cuando Recursos Humanos capturó la falta o la plataforma la derivó. Si el colaborador no tiene una relación laboral activa, el módulo lo indica en lugar de mostrar días. ## Solicitar una corrección [#correcciones] Si falta una checada por un error, por ejemplo porque el reloj no registró la entrada, el colaborador puede pedir que se agregue. La solicitud le llega a su jefe directo, así que solo está disponible para quien tiene un **Supervisor inmediato** asignado. La resuelve su jefe directo o Recursos Humanos, quien actúe primero. 1. En **Mi asistencia**, abre el menú del día (los tres puntos o clic derecho) y elige **Solicitar corrección**. También puedes hacerlo desde **Solicitudes → Nueva solicitud → Corrección de asistencia**. 2. Revisa la **Fecha de la checada** y escribe la **Hora**. 3. Elige el **Tipo de checada**: **Entrada** o **Salida**. Para la comida, usa **Salida** al salir y **Entrada** al volver. 4. Escribe el **Motivo** y haz clic en **Enviar solicitud**. ![Menú de un día con la opción Solicitar corrección](/images/guides/employee-portal/correction-menu.webp) ![Formulario para solicitar una corrección de checada](/images/guides/employee-portal/correction-request-form.webp) Se pueden corregir días del mes en curso y del mes anterior, nunca horas que todavía no ocurren. Para días más antiguos, el colaborador debe acudir a Recursos Humanos. Mientras está en revisión, el día muestra **Corrección pendiente**; después, **Corrección aprobada** o **Corrección rechazada**. **Ver detalle** abre la solicitud con el comentario de quien la resolvió. Si se aprueba, la checada se agrega a su asistencia. ![Día con una corrección pendiente](/images/guides/employee-portal/correction-pending.webp) ## Justificar un retardo [#justificaciones] Si la plataforma registró un retardo a partir de sus checadas y hubo un motivo, por ejemplo una cita médica, el colaborador puede pedir que se justifique. Igual que la corrección, solo está disponible para quien tiene un **Supervisor inmediato** asignado. 1. En **Mi asistencia**, abre el menú del día que tiene el retardo y elige **Justificar retardo**. La opción solo aparece en los días con un retardo que todavía se puede justificar. 2. Revisa el **Retardo**. Si el día tiene dos, elige el de la entrada o el del regreso de comer. 3. Escribe el **Motivo** y haz clic en **Enviar solicitud**. Se pueden justificar retardos del mes en curso y del mes anterior, siempre que no se hayan pagado en una nómina ya cerrada. Un retardo admite una sola justificación pendiente o aprobada a la vez. Junto al retardo, el día muestra **Justificación pendiente**; después, **Justificación aprobada** o **Justificación rechazada**. La resuelve su jefe directo o Recursos Humanos, quien actúe primero. Si se aprueba, el retardo se elimina de su asistencia. ## Incidencias [#incidencias] La pestaña **Mis incidencias** muestra en una tabla lo registrado en el mes elegido; las flechas junto al nombre del mes cambian el periodo. Cada renglón tiene su **Fecha**, la **Incidencia**, el **Tipo** (**Asistencia**, **Percepción** o **Deducción**), la **Cantidad** y las **Notas**: los días, horas o veces de una falta, un retardo o unas horas extra, o el importe de un bono o un préstamo. Las deducciones aparecen con signo negativo. El módulo es de solo lectura. Para aclarar una incidencia, el colaborador acude a Recursos Humanos. ## Solicitudes [#solicitudes] El módulo **Solicitudes** reúne todo lo que el colaborador pide. La pestaña **Mis solicitudes** muestra en una sola tabla sus solicitudes de tiempo libre, sus correcciones de asistencia y sus justificaciones de retardo, con el **Tipo**, la **Solicitud**, el **Estatus** y cuándo se solicitó. **Filtros** acota por tipo y estatus. Al hacer clic en una se abre su detalle; una solicitud de tiempo libre pendiente se puede cancelar desde su menú. El botón **Nueva solicitud** ofrece **Tiempo libre** y **Corrección de asistencia**. La corrección solo está disponible para quien tiene un **Supervisor inmediato** asignado. La justificación de un retardo se pide desde el día en **Asistencia**. ## Mi perfil [#mi-perfil] La pestaña **Mi perfil** muestra la información que la empresa tiene del colaborador, con su nombre, su número de empleado y su estatus en el encabezado. ![Módulo Mi perfil del portal con los datos personales, de identificación y de empleo](/images/guides/employee-portal/profile.webp) La información está agrupada en cuatro apartados: * **Datos personales**: fecha de nacimiento, nacionalidad, género, estado civil, correo electrónico y teléfonos. * **Identificación**: RFC, CURP y NSS (IMSS). * **Empleo**: razón social, puesto, departamento, sucursal, fecha de ingreso, fecha de antigüedad y supervisor. * **Datos bancarios**: banco, CLABE, número de cuenta y número de tarjeta. El perfil es de solo lectura. Para cambiar cualquier dato, un usuario de la plataforma lo edita en **Capital Humano → Empleados**. ## Nómina [#nomina] La pestaña **Mis recibos** lista los recibos de nómina timbrados del colaborador, del más reciente al más antiguo. Cada renglón muestra el periodo, la fecha de pago, el folio, el neto pagado y su estatus. El menú de opciones de cada renglón (o clic derecho sobre él) ofrece **Descargar PDF** y **Descargar XML** del comprobante timbrado. Un recibo cancelado aparece como **Cancelado**; cuando la empresa lo sustituye, el nuevo recibo ocupa su lugar en la lista. El botón **Filtros** permite acotar la lista por **Año** y por **Mes** de pago; los filtros activos aparecen como etiquetas que se pueden quitar una por una o todas con **Eliminar filtros**. Cada colaborador ve únicamente sus propios recibos, y los supervisores no ven los de su equipo. # Analítica (/docs/employees/analytics) **Analítica** reúne en una sola pantalla cómo evoluciona tu plantilla en un periodo. Está en **Capital humano → Analítica** y solo muestra información: no modifica ningún dato. Las cifras se calculan cada vez que abres la pantalla o modificas el periodo. ## Periodo [#periodo] Al entrar, la pantalla muestra los **últimos 12 meses**: desde el primer día del mes de hace 11 meses hasta hoy, según la zona horaria de tu organización. El periodo que se muestra aparece debajo del título de la gráfica **Plantilla en el tiempo**. Para modificarlo, abre **Filtros → Periodo** y elige la fecha inicial y la final. El periodo aparece como un filtro que puedes editar o eliminar; al eliminarlo regresas a los últimos 12 meses. * La fecha inicial no puede ser posterior a la final; puede ser el mismo día. * El periodo no puede ser mayor a **24 meses**. Si lo excedes, la pantalla indica **Periodo no válido** y explica el motivo. ## Qué empleados cuenta [#empleados] Cuenta a los empleados con ingreso en la fecha final del periodo o antes, que estén activos o que ya tengan fecha de baja. Los empleados pendientes de alta en nómina y los eliminados no se cuentan. Un empleado forma parte de la plantilla de un día cuando su fecha de ingreso es ese día o anterior y su fecha de baja no existe o es posterior. Es decir, el día de la baja ya no cuenta como plantilla. ## Plantilla y rotación [#rotacion] Las cuatro tarjetas superiores resumen el periodo: | Tarjeta | Cómo se calcula | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Plantilla al cierre** | Empleados en plantilla el último día del periodo. Debajo se indica cuántos había **al inicio del periodo**, es decir, el día anterior a la fecha inicial. | | **Altas** | Ingresos con fecha dentro del periodo, ambos días incluidos. | | **Bajas** | Terminaciones con fecha dentro del periodo, ambos días incluidos. | | **Rotación** | **Bajas entre la plantilla promedio**: las bajas del periodo divididas entre el promedio de la plantilla inicial y la final. Si ese promedio es 0, no se muestra porcentaje. | La gráfica **Plantilla en el tiempo** muestra los empleados activos al cierre de cada mes, con las altas y bajas de ese mes. El primer y el último mes se recortan al periodo, y el último punto es la fecha final. ## Altas, bajas y rotación por grupo [#grupos] La tabla **Altas, bajas y rotación** repite las cifras de las tarjetas para cada grupo, con las columnas **Nombre**, **Plantilla inicial**, **Altas**, **Bajas**, **Plantilla final** y **Rotación**. Puedes buscar por nombre y ordenar por cualquier columna; por defecto va primero el grupo con más plantilla final. Elige cómo agrupar en el selector **Agrupar por**: * **Departamento**. * **Unidad organizacional**: cada fila muestra debajo el tipo de unidad. Cuenta la unidad principal vigente del empleado. Si tiene varias unidades principales, cuenta en cada una. * **Supervisor**. El grupo siempre sale de la asignación **actual** del empleado, no de la que tenía en las fechas del periodo. Los grupos sin plantilla, altas ni bajas no aparecen. * **Sin asignar**: empleados que no tienen departamento, unidad principal o supervisor. * **Supervisor fuera de alcance**: el empleado tiene supervisor, pero este no está entre los empleados que tu acceso te permite ver, así que no se muestra su nombre. Todos los empleados en ese caso se suman en una sola fila con ese nombre. ## Antigüedad [#antiguedad] Agrupa a los empleados activos al cierre del periodo por años completos de antigüedad a esa fecha, a partir de su fecha de antigüedad o, si no tiene, de su fecha de ingreso. Los rangos son **Menos de 1 año**, **1 a 3 años**, **3 a 5 años**, **5 a 10 años** y **Más de 10 años**. Quien cumple exactamente 1, 3, 5 o 10 años queda en el rango superior. ## Distribución salarial [#salarios] Agrupa a los empleados activos al cierre del periodo por su **salario diario actual**, no por el que tenían en esa fecha. Los rangos son **Menos de $400**, **$400 a $599**, **$600 a $899**, **$900 a $1,299**, **$1,300 a $1,999** y **$2,000 o más**. Encima de la gráfica aparecen el **Promedio diario** y la **Mediana diaria**. Si no hay salarios registrados, la tarjeta lo indica. ## Ausentismo [#ausentismo] La gráfica **Ausentismo** muestra los días de ausencia por mes y tipo de incidencia: **Faltas injustificadas**, **Faltas justificadas**, **Permisos sin goce**, **Incapacidad por enfermedad**, **Incapacidad por riesgo de trabajo** e **Incapacidad por maternidad**. Las vacaciones y los demás tipos de incidencia no cuentan. * Solo se suman las incidencias **aprobadas**. * Una incidencia cuenta únicamente si su fecha cae entre el ingreso del empleado y su fecha de baja, ambos días incluidos. * Encima de la gráfica aparecen los **Días de ausencia** del periodo y la **Tasa de ausentismo**: los días de ausencia entre los días-empleado del periodo. Los días-empleado suman, por cada empleado, los días del periodo en que estuvo en nómina; el día de la baja se cuenta como el último día trabajado. Sin días-empleado no se muestra tasa. ## Horas extra [#horas-extra] La gráfica **Horas extra** suma por mes las horas extra **aprobadas**, separadas en **Horas dobles** y **Horas triples**. ## Qué ves según tus permisos [#permisos] Cada sección aparece solo si tu rol te da acceso a ella. Las que no te corresponden no se muestran, no aparecen en cero. Tu acceso por razón social o unidad organizacional también limita los empleados que se cuentan. | Sección | Permiso que necesitas | | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | Tarjetas, plantilla en el tiempo, tabla de grupos y antigüedad | Ver empleados. | | Distribución salarial | Ver empleados y ver sueldos. Se cuentan solo los empleados que alcanzas con ambos permisos. | | Ausentismo y horas extra | Ver incidencias. | Para entrar a la pantalla necesitas poder ver empleados; sin ese permiso regresas al **Dashboard**. Si tienes conectado un asistente de IA, también puede consultar esta analítica con el mismo alcance que tú. # Edición masiva y exportaciones (/docs/employees/bulk-edit) Cuando necesitas cambiar un dato en muchos empleados, como el departamento, el calendario o el sueldo, la edición masiva te deja hacerlo en una sola hoja en lugar de abrir a cada persona. Las exportaciones descargan la lista de empleados para trabajarla fuera de la plataforma. Ambas opciones están en **Capital Humano → Empleados**, en la flecha junto al botón **Empleado**. ![Menú del botón Empleado con Exportar como Excel, Exportar como CSV y Edición masiva](/images/guides/employees/employee-add-menu.webp) ## Exportar la lista de empleados [#exportar] * **Exportar como Excel** descarga un archivo de Excel con los empleados visibles en la tabla. * **Exportar como CSV** descarga un archivo CSV con los mismos empleados. **Los archivos traen las columnas de la edición masiva.** El Excel y el CSV tienen exactamente las mismas columnas que la hoja de [edición masiva](#hoja), en el mismo orden y con los mismos valores que ves en ella. En el Excel, las columnas con opciones, como **Razón social**, **Tipo de trabajador** o **Sindicalizado**, traen una lista desplegable para elegir el valor. **La exportación respeta los filtros.** Si tienes filtros aplicados, solo se incluyen los empleados que coinciden con ellos. El diálogo de descarga te lo recuerda; revisa los filtros antes de hacer clic en **Descargar**. **Sin empleados, descargas una plantilla.** Mientras tu organización no tiene ningún empleado en nómina ni sin alta en nómina, el menú muestra **Plantilla en Excel** y **Plantilla en CSV** en lugar de las exportaciones. Cada una descarga al momento un archivo `plantilla-empleados` que solo trae los encabezados de las mismas columnas que su exportación. ## La hoja de edición masiva [#hoja] **Edición masiva** abre la hoja **Edición masiva de empleados**: una fila por empleado y una columna por dato. Los encabezados con asterisco son obligatorios. Si además el nombre está subrayado con puntos, la columna solo es obligatoria en ciertos casos: pasa el cursor sobre el encabezado para ver cuándo, por ejemplo **NSS** para dar de alta en nómina a un empleado que aporta al IMSS. ![Hoja de edición masiva con el panel Borrador a la derecha](/images/guides/employees/bulk-edit.webp) Puedes editar, entre otros: * **Número interno**, si tu organización lo tiene habilitado. * Datos personales: nombre, apellidos, RFC, CURP, fecha de nacimiento, género, estado civil, nacionalidad, teléfonos, zona horaria y correo electrónico. * Domicilios: el **Domicilio real** y el **Domicilio fiscal**, con la casilla **Domicilio fiscal igual al real**. Con la casilla marcada, el domicilio fiscal se toma del real, y el código postal que pide la alta en nómina se revisa en la columna del domicilio real. Si editas un dato del domicilio fiscal de un empleado con la casilla marcada sin tocar la casilla, se desmarca. En las filas nuevas viene desmarcada. * **Acceso al portal de colaborador** y **Método de acceso al portal** (**Correo** o **Teléfono**). Con acceso, el método pide su dato: el correo electrónico o el teléfono. El teléfono no se puede repetir entre empleados que entran con teléfono. * Datos bancarios: CLABE interbancaria, banco, número de cuenta y número de tarjeta. * Empleo: razón social, entidad del centro de trabajo, registro patronal, puesto, departamento, sucursal, centro de costo, supervisor inmediato, fecha de ingreso, antigüedad, **Último día laborado**, **Baja existente IMSS**, NSS, tipo de trabajador, si aporta al IMSS, **Alta existente IMSS**, tipo de trabajador IMSS, semana o jornada reducida y zona económica. * Nómina: sueldo diario, régimen SAT, calendario de nómina, contrato, horario y si está **Activo** en nómina. * Tus campos personalizados. Escribe directamente en las celdas o pega un bloque copiado de otra hoja de cálculo. Los botones de deshacer y rehacer, la búsqueda y **Filtros** funcionan igual que en la lista. Las fechas se escriben en formato `dd/mm/aaaa`: basta con teclear los números, las diagonales se agregan solas y arriba de la celda aparece la fecha completa para confirmarla. Al pegar también se aceptan fechas `aaaa-mm-dd`. Para capturar empleados nuevos, usa **Agregar filas** y llena las filas vacías que aparecen al final. Para dar de baja a un empleado, captura la fecha en su columna **Último día laborado**. Una fila nueva que ya trae último día laborado se crea dada de baja; ese día no puede ser posterior a hoy ni anterior a su fecha de ingreso, y la fila no puede estar marcada como **Activo**. **Altas y bajas ante el IMSS.** Las columnas **Alta existente IMSS** y **Baja existente IMSS** indican, con **Sí** o **No**, si el movimiento ya se presentó ante el IMSS fuera de la plataforma; la guía [Movimientos IMSS](/docs/employees/imss-movements#existentes) explica qué cambia en cada caso. * En una fila nueva que aporta al IMSS, **Alta existente IMSS** es obligatoria. Si la fila trae último día laborado, responde también **Baja existente IMSS**. * En un empleado existente, **Alta existente IMSS** muestra la respuesta actual y puedes cambiarla. Si su alta ya se confirmó como presentada en Movimientos IMSS, la celda no se puede editar y dice **No (presentada en la plataforma)**. * Si capturas el **Último día laborado** de un empleado existente que aporta al IMSS, responde también **Baja existente IMSS**, en la columna de al lado. Sin respuesta, la validación marca un error en esa celda. ## Revisar y publicar [#publicar] Ningún cambio se aplica mientras editas: todo queda en un borrador. El panel **Borrador**, a la derecha, tiene dos apartados: * **Cambios**: cada fila modificada, marcada como **Nuevo**, **Modificado** o **Baja**, con los campos que cambian y su valor antes y después. * **Errores**: los datos que faltan o no son válidos, con la fila y el campo. Al validar o publicar, cada celda con error se marca en rojo en la hoja hasta la siguiente validación. Al seleccionar una celda en rojo, el panel te lleva a su error y lo resalta. El borrador es uno por organización. Si sales y regresas, sigue ahí con tus cambios. 1. Haz clic en **Validar**. La plataforma revisa las filas modificadas y lista los errores. Corrígelos y vuelve a validar. 2. Cuando el panel indique **Borrador validado**, haz clic en **Publicar**. 3. El diálogo **Confirmar publicación** resume cuántos empleados se crearán, cuántos se modificarán y cuántos se darán de baja, con una muestra de los cambios. 4. Escribe **PUBLICAR** y confirma. Si editas una celda después de validar, tendrás que validar de nuevo antes de publicar. Si alguien activa o desactiva **Habilitar número interno** mientras tienes la edición masiva abierta, al validar o publicar aparece el aviso "La configuración del número interno cambió. Revisa la columna antes de publicar.": la columna **Número interno** se muestra u oculta según la configuración nueva, no se publica nada y tienes que validar de nuevo. **La publicación es todo o nada.** Si una sola fila falla al publicar, no se aplica ninguna y el borrador se conserva para que lo corrijas. Al validar se revisan los datos obligatorios, los formatos y los datos que no se pueden repetir en la organización, como el RFC, la CURP, el NSS o el número interno. Al publicar se aplican además las mismas reglas que al [editar a un empleado](/docs/employees/hiring#bloqueos): con una nómina pagada o el alta ante el IMSS ya presentada, la razón social, el tipo de trabajador, si aporta al IMSS, el registro patronal y la fecha de ingreso no se pueden cambiar; el calendario de nómina no cambia mientras el empleado está en una nómina abierta; la fecha de ingreso se puede cambiar, pero no borrar; y el tipo de trabajador no pasa de subordinado a asimilado u honorarios si el empleado tiene créditos INFONAVIT o FONACOT activos. Cambiar la fecha de ingreso mueve también su alta ante el IMSS si sigue pendiente. ## Descartar el borrador [#descartar] **Descartar todo** elimina todos los cambios pendientes y los empleados vuelven a sus valores originales. No se puede deshacer. ## Exportar desde la hoja [#exportar-desde-la-hoja] Los botones **Exportar Excel** y **Exportar CSV** de la hoja descargan los empleados visibles incluyendo los cambios no guardados del borrador. Sirven para revisar el borrador fuera de la plataforma antes de publicarlo. # Créditos y descuentos (/docs/employees/credits) Los créditos son descuentos recurrentes que la nómina aplica sola en cada periodo: un aviso de retención del INFONAVIT, una cédula del FONACOT, un préstamo de la empresa, una orden de pensión alimenticia o una caja de ahorro. Se registran por empleado y cada uno se descuenta con su concepto de nómina. ## Dónde se registran [#donde] Los créditos se registran en la sección **Créditos** del [formulario del empleado](/docs/employees/hiring#secciones), al darlo de alta o al editarlo: 1. Entra a **Capital Humano → Empleados** y abre al empleado con **Editar**, o empieza un alta nueva con **Empleado**. 2. Abre la sección **Créditos**. En un alta nueva, marca su casilla. 3. Haz clic en **Agregar crédito**, llena el formulario y haz clic en **Agregar crédito**. 4. Guarda al empleado con **Guardar cambios**, o con **Crear empleado** en un alta nueva. **El crédito se guarda junto con el empleado.** Mientras no guardes al empleado, el crédito aparece como **Sin guardar**; si sales del formulario sin guardar, se descarta. En un alta nueva, si desmarcas la sección **Créditos**, el empleado se crea sin ellos. Al editar, la sección Créditos solo aparece si el empleado tiene una relación laboral abierta. Los créditos también se consultan en el panel del empleado y en la vista **Créditos** de la sección Nómina de su [página](/docs/employees/profile#nomina). ![Sección Créditos del alta de empleado, sin créditos capturados](/images/guides/employees/employee-form-credits.webp) Un crédito INFONAVIT o FONACOT activo o suspendido solo puede tenerlo el personal subordinado: mientras exista, el **Tipo de trabajador** no puede cambiar a asimilado u honorarios. ## Tipos de crédito [#tipos] El formulario empieza por el **Tipo de crédito**; los demás campos dependen de él. * **Crédito INFONAVIT** * **Crédito FONACOT** * **Préstamo interno** * **Pensión alimenticia** * **Caja de ahorro** * **Otro** INFONAVIT, FONACOT, pensión alimenticia y caja de ahorro usan siempre el mismo concepto de nómina, y el formulario te dice cuál. En préstamo interno y otro, si tu catálogo tiene más de un concepto para ese tipo, eliges cuál usar. Todos piden la **Fecha de inicio** y aceptan una **Referencia del documento** y **Notas** opcionales. El **Folio** es opcional, pero no se puede repetir: dos créditos del mismo tipo no pueden tener el mismo folio en la organización. ## Crédito INFONAVIT [#infonavit] Captura los datos tal como vienen en el aviso de retención: * **Tipo de descuento**: **Monto fijo**, **Veces salario mínimo (VSM)** o **Porcentaje** del salario base de cotización. * El monto, el factor de VSM o el porcentaje, según el tipo elegido. En VSM, la mensualidad se calcula sobre la Unidad Mixta Infonavit vigente, así que se actualiza sola cada año. * **Base del descuento**: **Mensual**, que se prorratea entre los periodos reales del mes, o **Por periodo**. Además del descuento del crédito, a quien tiene un crédito INFONAVIT la nómina le descuenta el **Seguro de vivienda INFONAVIT**: $15.00 por bimestre, una sola vez en una de las nóminas ordinarias del bimestre. ## Crédito FONACOT [#fonacot] FONACOT siempre descuenta un monto fijo mensual que se prorratea entre los periodos reales del mes. ![Formulario Agregar crédito para un crédito FONACOT](/images/guides/employees/credit-form.webp) Captura el **Monto mensual a descontar** de la cédula y, en **Capturar**, elige qué otro dato traes: * **Plazo en meses**: captura el **Plazo (meses)** y la plataforma calcula el monto total del crédito. * **Monto total**: captura el **Monto total del crédito** y la plataforma calcula el plazo. Un mes en que el empleado no tiene salario suspende la retención: no se cobra, no queda a deber y el crédito termina un mes después. El monto mensual nunca cambia. ## Préstamo interno y otro [#prestamo] En **Forma de descuento** eliges cómo se cobra: * **Monto a descontar**: una cantidad fija, **Mensual** o **Por periodo**. El **Monto total** es opcional. * **En plazos**: captura el **Monto total** y el número de **Plazos**. Cada plazo es un periodo de nómina; la plataforma calcula el monto de cada pago y ajusta el último al saldo restante. ## Pensión alimenticia [#pension] Captura lo que ordena el juzgado: * **Base de la pensión**: un porcentaje de las percepciones totales, de conceptos seleccionados, del salario nominal, del neto o del neto después de ISR; un **Monto fijo** en pesos; o una **Cantidad en UMAs**. * El porcentaje, el monto o las UMAs, según la base. * **Base del descuento**: **Mensual** o **Por periodo**. * Si eliges conceptos seleccionados, marca las percepciones sobre las que se calcula. * Los datos del beneficiario que recibe la pensión. * **Número de expediente** y **Texto del oficio**, opcionales. En la pensión, el número de expediente ocupa el lugar de la referencia del documento. ## Caja de ahorro [#caja-de-ahorro] En **Tipo de retención** eliges un porcentaje del salario, que se retiene cada periodo, o un monto fijo, que puede ser **Mensual** o **Por periodo**. La **Meta de ahorro** es opcional: al alcanzarla, la retención se detiene y el ciclo se liquida solo. Déjala vacía para un ahorro abierto. ## Estatus y seguimiento [#estatus] Cada crédito muestra su tipo, su folio, su estatus y, cuando tiene monto total, una barra de avance con lo descontado y el saldo. El menú de cada crédito tiene estas opciones: * **Editar** cambia sus condiciones mientras está activo o suspendido. * **Ver calendario** muestra, en créditos FONACOT y préstamos en plazos, lo esperado contra lo aplicado en cada mes o pago, y el saldo. * **Suspender** deja de aplicar el descuento en los periodos que abras a partir de ese momento. Las nóminas ya abiertas conservan lo que tenían. **Reactivar** lo vuelve a aplicar. * **Marcar como liquidado** cierra el crédito y deja de descontarse. * **Cancelar** lo cancela y deja de descontarse. Los estatus posibles son **Activo**, **Suspendido**, **Liquidado** y **Cancelado**. Un crédito liquidado o cancelado conserva su historial de pagos, pero ya no se puede editar ni reactivar. # Campos personalizados (/docs/employees/custom-fields) Los campos personalizados guardan información que la plataforma no trae de fábrica y que tu organización necesita de cada empleado. Se capturan en la sección **Campos adicionales** del [formulario del empleado](/docs/employees/hiring#secciones), que solo aparece cuando existe al menos un campo activo, y también aparecen como columnas en la [edición masiva](/docs/employees/bulk-edit). ## Crear un campo [#crear] 1. Entra a **Configuración → Capital Humano → Campos personalizados** y haz clic en **Agregar campo**. 2. Escribe el **Nombre** que verán los usuarios en el formulario, por ejemplo "Talla de uniforme". 3. Revisa la **Clave**. Se llena sola a partir del nombre mientras no la cambies. 4. Elige el **Tipo**. No se puede cambiar después de crear el campo. 5. Si quieres, agrega una **Descripción**. 6. Marca las casillas de requerido que apliquen, define el **Orden** y deja **Activo** encendido. 7. Haz clic en **Crear campo**. ![Diálogo Agregar campo personalizado con nombre, clave y tipo](/images/guides/employees/custom-field-form.webp) ## Tipos de campo [#tipos] * **Texto**: cualquier texto libre. * **Número**. * **Fecha**. * **Booleano**: sí o no. * **Selección**: una lista de opciones que tú defines. Agrega al menos una. ## Reglas de la clave [#clave] La clave identifica al campo en las exportaciones y en las integraciones, por eso tiene reglas estrictas: * Solo minúsculas, números y guiones bajos, y debe iniciar con una letra. Por ejemplo, **talla\_uniforme**. * Máximo 60 caracteres. * No se puede repetir dentro de la organización. * **No se puede cambiar después de crear el campo.** Elígela con cuidado. ## Campos requeridos [#requeridos] * **Requerido al dar de alta**: el formulario no permite crear empleados sin este campo. Mientras exista un campo así que no sea booleano, la sección **Campos adicionales** de un alta nueva viene marcada y no se puede desmarcar. Un campo booleano requerido sin respuesta se guarda como **No**. * **Requerido para nómina**: marca el campo como necesario para la nómina. Por ahora es solo una marca: no impide calcular la nómina de un empleado que no lo tenga. La lista de campos muestra ambas marcas en las columnas **Requerido en alta** y **Requerido en nómina**. ## Orden, desactivar y eliminar [#administrar] El **Orden** decide en qué posición aparece el campo en la sección Campos adicionales: los números menores van primero. Desde el menú de cada campo puedes: * **Editar** su nombre, descripción, opciones, marcas de requerido y orden. La clave y el tipo quedan fijos desde que se crea. * **Desactivar** el campo para que deje de aparecer en el formulario sin borrarlo. **Activar** lo vuelve a mostrar. * **Eliminar** el campo. Los valores ya capturados se conservan para auditoría, pero el campo deja de aparecer en formularios y listados. # Alta de empleados (/docs/employees/hiring) El alta se hace en **Capital Humano → Empleados**, con el botón **Empleado**. El formulario ocupa toda la página: arriba está **Registro**, con lo mínimo para guardar al empleado, y debajo las demás secciones, cada una con su casilla. Si es tu primer empleado, la guía [Primeros pasos](/docs/getting-started#primer-empleado) te lleva por los datos mínimos paso a paso. Mientras capturas, el formulario guarda en tu navegador lo que llevas y las secciones que marcaste: puedes salir y regresar sin perder lo capturado. **Limpiar** borra ese borrador y deja el formulario vacío. ## Datos para guardar al empleado [#datos-obligatorios] La sección **Registro** siempre se guarda. Para crear el registro, aunque todavía no esté listo para nómina, necesitas: * **Nombre** y **Apellido paterno**. * **RFC** y **CURP**. * **Razón social** y **Tipo de trabajador**: **Subordinado**, **Asimilado a salarios** u **Honorarios**. * **Fecha de ingreso**. * **Entidad del centro de trabajo**, el estado donde labora. Determina el impuesto sobre nómina. * **Registro patronal** y la respuesta a **Alta existente IMSS**, si el empleado aporta al IMSS. * **Correo electrónico** o **Teléfono**, si le das acceso al portal de colaborador: el que corresponda a su **Método de acceso**. * Los campos personalizados marcados como requeridos al dar de alta, si tu organización tiene alguno. Registro también tiene el **Segundo nombre**, el **Apellido materno**, el **Correo electrónico**, el **Teléfono**, la **Fecha de antigüedad** junto a la **Fecha de ingreso** (solo para personal subordinado; no puede ser posterior a hoy) y, si tu organización lo utiliza, el **Número interno**. Si falta alguno, el formulario te lleva al campo y lo marca con su error. ![Sección Registro del alta de empleado con la lista de secciones a la derecha](/images/guides/getting-started/employee-form-registration.webp) El RFC, la CURP, el NSS, el correo electrónico y el número interno no se pueden repetir dentro de la organización. Si capturas un RFC, una CURP, un NSS o un correo que ya tiene otro empleado, el campo lo marca en cuanto terminas de escribir. Al guardar, si el RFC, la CURP o el NSS ya es de otro empleado, un aviso te muestra de quién es. Lo mismo pasa con el folio de un crédito INFONAVIT o FONACOT: el formulario del crédito te avisa si otro empleado ya lo tiene. Tampoco se puede repetir el teléfono entre empleados que entran al portal con teléfono. ## Secciones del formulario [#secciones] Fuera de Registro, cada sección empieza sin marcar. Marca su casilla para capturarla. **Solo se guarda lo que está en secciones marcadas**: si capturas datos y desmarcas la sección, los datos siguen ahí si vuelves a marcarla, pero el empleado se guarda sin ellos. Los campos obligatorios de una sección solo se piden mientras está marcada. **Datos personales.** La **Foto** del empleado, opcional: PNG, JPG o WEBP de hasta 5 MB. Se muestra en el panel, en la página del empleado, en el portal de colaborador y en la app móvil. Después, fecha de nacimiento, género, estado civil, nacionalidad, país, estado y municipio de nacimiento, teléfono secundario y zona horaria. La zona horaria, si no eliges una, es la de la organización. La nacionalidad se elige del catálogo de nacionalidades: escribe para buscarla, por ejemplo "Mexicana". **Domicilio.** El domicilio donde vive el empleado: **Calle**, **Núm. exterior**, **Núm. interior**, **Colonia**, **Estado** y **Municipio o alcaldía**. Si en **Alta a nómina** está marcada **Domicilio fiscal distinto al domicilio**, aquí aparece también el **Domicilio fiscal**, como viene en su Constancia de Situación Fiscal. **Alta a nómina.** Los datos que la nómina necesita, y la casilla **Dar de alta en nómina**, que se explica [más abajo](#dar-de-alta-en-nomina): * **Sueldo bruto**, que capturas en el periodo que prefieras. La plataforma lo guarda como sueldo diario, y el sueldo diario no puede ser menor al salario mínimo vigente. * **Calendario de nómina**, **Contrato** y **Tipo de jornada**. * **Régimen fiscal (SAT)**. * **NSS (IMSS)** de 11 dígitos, si el empleado aporta al IMSS. La plataforma revisa su dígito verificador y no deja guardar un NSS mal capturado. * **Tipo de trabajador IMSS** (**Permanente**, **Eventual de la ciudad**, **Eventual de la construcción** o **Eventual del campo**) y **Semana o jornada reducida** (**Semana completa**, semana reducida de 1 a 5 días o **Jornada reducida**), si el empleado aporta al IMSS. Son los datos que el IMSS pide para el trabajador y no tienen que ver con el **Tipo de trabajador** de la sección Registro. * **Código postal**, el que va en el recibo de nómina. Es el del domicilio del empleado. Si su Constancia de Situación Fiscal trae otro domicilio, marca **Domicilio fiscal distinto al domicilio** y captura el **Código postal fiscal**. Solo aparecen los calendarios y contratos de la razón social elegida. **Organización.** **Puesto**, **Departamento**, **Supervisor inmediato**, **Sucursal**, **Centro de costos** y una unidad de cada tipo de tu [estructura organizacional](/docs/configuration/organization-structure). El supervisor inmediato es quien verá a este empleado en su equipo dentro del portal de colaborador. **Contratación.** **Zona económica**, que determina el salario mínimo que aplica; **Tipo de salario**, que es opcional, informativo y puede ser **Fijo**, **Mixto** o **Variable**; **Aplica ajuste anual de ISR**, solo para empleados de sueldos y salarios; y **Sindicalizado**. Para registrar a alguien que ya se fue, por ejemplo al migrar desde otro sistema, marca **Empleado dado de baja** al final de la sección: pide el **Último día laborado**, que no puede ser posterior a hoy ni anterior a la fecha de ingreso, y, si el empleado aporta al IMSS, **Baja existente IMSS**. El empleado se crea dado de baja, así que **Dar de alta en nómina** se desmarca y no se puede marcar mientras la casilla siga marcada. **Datos bancarios.** **Forma de pago**: **Transferencia** o **Efectivo**. Después, **CLABE interbancaria** de 18 dígitos, **Banco**, **Número de cuenta** y **Número de tarjeta**. Al capturar la CLABE, el banco se llena solo. Si el banco no está en la lista, elige **Otro** y escribe el **Nombre del banco**. Un número de cuenta sin CLABE necesita el banco, porque el recibo lo nombra. **Horario.** Un horario o una rotación. Con una rotación, elige en **Inicia en el paso** dónde arranca el empleado dentro del ciclo. Si no marcas la sección, aplica el horario predeterminado de la organización. **Campos adicionales.** Aparece solo si tu organización tiene [campos personalizados](/docs/employees/custom-fields). **Créditos.** INFONAVIT, FONACOT, préstamos y otros descuentos. Se guardan junto con el empleado; la guía [Créditos y descuentos](/docs/employees/credits) lo explica. **Notas.** Notas internas sobre el empleado. Se muestran en el panel del empleado y en la sección General de su página. ![Secciones Alta a nómina y Datos personales marcadas en el alta de empleado](/images/guides/employees/employee-form-sections.webp) ## Navegar el formulario [#navegar] En pantallas anchas, a la derecha del formulario está la lista de secciones con el nombre del empleado. Haz clic en una para ir a ella; las secciones sin marcar aparecen atenuadas. Junto a cada sección, una marca indica si ya tiene todos sus campos, si le falta alguno o si tiene errores después de intentar guardar. Cada sección marcada tiene el enlace **Resaltar campos pendientes**, que marca por unos segundos los campos que le faltan. Cuando la sección ya tiene todos sus campos, el enlace se desactiva. ## Dar de alta en nómina [#dar-de-alta-en-nomina] Un empleado entra al cálculo de nómina solo cuando está **En nómina**. Para darlo de alta, marca la sección **Alta a nómina** y, dentro de ella, **Dar de alta en nómina**. En un alta nueva la casilla siempre empieza sin marcar. ![Sección Alta a nómina con la casilla Dar de alta en nómina](/images/guides/getting-started/employee-form-payroll.webp) Con la casilla marcada, estos datos son obligatorios: * Razón social, RFC, CURP, entidad del centro de trabajo y código postal. * Sueldo, régimen fiscal y tipo de jornada. * Calendario de nómina y contrato. * NSS, tipo de trabajador IMSS y semana o jornada reducida, si el empleado aporta al IMSS. Si al guardar falta alguno, un aviso lista los campos pendientes y el formulario te lleva al primero. Captúralos o desmarca la casilla. Si guardas sin marcar la casilla, el empleado queda **Sin alta en nómina** y aparece en la vista **Sin alta en nómina** de la lista. Para darlo de alta más tarde, ábrelo con **Editar** en el menú de su fila o en la sección General de su [página](/docs/employees/profile#general), completa lo que falte y marca la casilla. **El alta en nómina nunca es automática.** Completar los datos no da de alta al empleado: siempre hace falta marcar la casilla. En cambio, si a un empleado en nómina se le borra uno de esos datos, regresa a **Sin alta en nómina** por sí solo. ## Acceso al portal de colaborador [#acceso-al-portal] La casilla **Acceso al portal de colaborador**, en Registro, viene marcada en un alta nueva. Debajo, en **Método de acceso**, eliges cómo entra el empleado: **Correo** (por omisión) o **Teléfono**, con un código por WhatsApp. El método elegido pide su dato: el **Correo electrónico** o el **Teléfono** de Registro; si la casilla está marcada y ese dato está vacío, no podrás guardar. Si no quieres darle acceso, desmárcala. La guía [Portal de colaborador](/docs/employee-portal) explica cómo entra y qué ve. ## Guardar [#guardar] * **Crear empleado** guarda y regresa a la lista, con el empleado nuevo resaltado. * **Crear otro** guarda y deja el formulario vacío para el siguiente empleado. El aviso de que el empleado se creó tiene **Ver perfil**, que abre la sección General de su [página](/docs/employees/profile#general). **Cancelar** regresa a la lista sin guardar. **Movimiento ante el IMSS.** Al registrar un empleado nuevo que cotiza al IMSS se crea su alta en [Movimientos IMSS](/docs/employees/imss-movements). Con **Alta existente IMSS** en **No**, el alta queda pendiente con su fecha límite de presentación; con **Sí**, queda registrada como ya presentada. Si lo registras como **Empleado dado de baja**, su baja sigue la respuesta de **Baja existente IMSS**. ## Editar a un empleado [#editar] Al editar, el formulario tiene las mismas secciones, más **SDI/SBC** para el personal subordinado con relación laboral abierta, si puedes consultar salarios. Créditos aparece solo si el empleado tiene una relación laboral abierta. Aquí las secciones no tienen casilla: se abren y se cierran con un clic en su nombre, y **cerrar una sección no borra sus datos**. Al entrar se abren las secciones que ya tienen datos y la que elegiste con **Editar** desde la página del empleado. ![Edición de un empleado con las secciones como acordeones y su marca en la lista](/images/guides/employees/employee-edit-form.webp) **Guardar cambios** guarda y regresa a la lista, con el empleado resaltado; el aviso de guardado trae **Ver perfil** para abrir su [página](/docs/employees/profile#general). **Cancelar** regresa a la página del empleado sin guardar. Si un cambio afecta algo más que el registro del empleado, por ejemplo una nómina abierta, recibos ya timbrados o una dispersión en borrador, antes de guardar aparece **Revisar el efecto de los cambios** con cada efecto. Haz clic en **Guardar** para continuar o en **Volver** para seguir editando. El formulario de edición no muestra el último día laborado: para dar de baja a un empleado, utiliza [Dar de baja](/docs/employees/terminating). **Alta existente IMSS.** Registro muestra la respuesta según el alta del empleado, y puedes cambiarla; la guía [Movimientos IMSS](/docs/employees/imss-movements#existentes) explica qué pasa con el movimiento. Si el alta ya se confirmó como presentada en Movimientos IMSS, solo verás la respuesta **No**, sin poder cambiarla. ### Datos que ya no se pueden cambiar [#bloqueos] Cuando el empleado ya tiene una nómina pagada o su alta ante el IMSS ya se presentó, **Razón social**, **Tipo de trabajador**, **Aporta al IMSS** y **Registro patronal** quedan bloqueados: esos datos solo cambian con una [baja](/docs/employees/terminating) y un [reingreso](/docs/employees/rehiring). Al pasar el cursor sobre un campo bloqueado ves por qué. * **Calendario de nómina** se bloquea mientras el empleado está en una nómina abierta. Ciérrala o cancélala primero. * **Tipo de trabajador** tampoco puede pasar de subordinado a asimilado u honorarios mientras el empleado tiene créditos INFONAVIT o FONACOT activos. * Las fechas de un empleado dado de baja solo cambian al reactivarlo. ### Corregir la fecha de ingreso [#corregir-fecha-de-ingreso] Con una nómina pagada o el alta ya presentada, la **Fecha de ingreso** no se cambia en el formulario: junto a ella aparece **Corregir**. 1. Haz clic en **Corregir**. 2. Captura la **Fecha de ingreso** correcta. No puede ser posterior a hoy. 3. Escribe el **Motivo**, hasta 500 caracteres. Queda en el historial del empleado. ![Diálogo Corregir fecha de ingreso con la fecha y el motivo](/images/guides/employees/hire-date-correction-dialog.webp) 4\. Haz clic en **Corregir**. Si la corrección cambia lo que se pagó en periodos ya cerrados, primero aparece **Confirmar cambio retroactivo** con esos periodos. Al confirmar, la diferencia se registra como un ajuste retroactivo en la siguiente nómina. Un alta ante el IMSS todavía pendiente toma la nueva fecha. Si el alta ya se presentó, conserva la fecha anterior: es necesario corregirla en el IDSE. ### Cambiar el sueldo [#cambiar-sueldo] Al cambiar el **Sueldo bruto** aparece **Fecha de aplicación del sueldo**, el día desde el que aplica el sueldo nuevo. La fecha no puede ser anterior a la fecha de ingreso ni caer en un periodo de nómina ya cerrado, y llega hasta hoy. Si la fecha de ingreso todavía no llega, el sueldo aplica desde ese día. Si la dejas vacía, aplica desde la última fecha permitida, que el campo muestra. Si la fecha tiene más de 5 días hábiles de atraso, **Revisar el efecto de los cambios** te avisa que el IDSE rechaza el aviso de modificación de salario fuera de ese plazo. ## Fecha de ingreso en un periodo cerrado [#periodo-cerrado] Si la fecha de ingreso cae dentro de un periodo de nómina que ya se cerró, al guardar aparece **Confirmar cambio retroactivo** con los periodos afectados. Lo ya pagado no se modifica: la diferencia se registra como un ajuste retroactivo que se aplica en la siguiente nómina, donde puedes revisarlo o cancelarlo antes del cierre. Haz clic en **Confirmar ajuste** para continuar o en **Cancelar** para corregir la fecha. ![Diálogo Confirmar cambio retroactivo por una fecha de ingreso en un periodo cerrado](/images/guides/payroll/retro-confirm-dialog.webp) Consulta [el cierre del periodo](/docs/payroll/closing) para saber qué implica que un periodo esté cerrado. # Movimientos IMSS (/docs/employees/imss-movements) Cada alta, baja y cambio de salario de un empleado que cotiza al IMSS debe presentarse ante el instituto dentro de un plazo. La plataforma crea un **movimiento** por cada uno, con su fecha límite, y los reúne en **Capital humano → Movimientos IMSS**. ## Qué genera un movimiento [#que-genera] * **Alta**: al registrar un empleado o al [reactivarlo](/docs/employees/rehiring). Utiliza el SBC que el empleado tiene en su fecha de ingreso. Si la plataforma no puede calcularlo, por ejemplo porque no hay UMA para esa fecha, el alta se guarda sin SBC y la columna **Detalle** lo marca como faltante. Si después se [corrige la fecha de ingreso](/docs/employees/hiring#corregir-fecha-de-ingreso) mientras el alta sigue pendiente, el alta toma la nueva fecha, su fecha límite y el SBC de esa fecha. Un alta ya presentada conserva la fecha anterior y hay que corregirla en el IDSE. * **Baja**: al [dar de baja](/docs/employees/terminating) a un empleado, desde cualquier pantalla. * **Modificación de salario**: cada vez que cambia el SBC del empleado. Guardar sin cambiar el sueldo no genera nada. Al aprobar la propuesta del [promedio variable bimestral](/docs/calculation/sbc#parte-variable), la modificación lleva la fecha del primer día del bimestre siguiente. Si recalculas ese bimestre con cifras distintas mientras la modificación sigue pendiente, la plataforma la elimina y la propuesta vuelve a quedar pendiente. Si ya la presentaste, se conserva y la propuesta indica el SBC que resulta con las cifras nuevas, para que presentes la corrección. Los asimilados y los trabajadores por honorarios no cotizan al IMSS, así que no generan movimientos. Los empleados que ya existían antes de esta función tampoco aparecen: solo se registran los movimientos nuevos. ## Altas y bajas que ya existen [#existentes] Cuando registras, das de baja o reactivas a un empleado que aporta al IMSS, la plataforma te pregunta si ese movimiento ya se presentó ante el IMSS fuera de la plataforma, por ejemplo porque migras desde otro sistema. La pregunta es **Alta existente IMSS** en el alta y el reingreso, y **Baja existente IMSS** en la baja, también al registrar a un empleado que ya se dio de baja. * **No**: el movimiento entra a **Pendientes** con su fecha límite, como cualquier otro. * **Sí**: el movimiento se registra como ya presentado y aparece directo en **Presentados**, con la etiqueta **Alta existente IMSS** en la columna **Detalle**. No tiene fecha límite que vigilar, no pide confirmación y la plataforma no calcula su SBC. Puedes cambiar la respuesta del alta al editar al empleado, en la sección **Registro**, mientras no la hayas confirmado como presentada desde esta pantalla. Si la cambias de **Sí** a **No**, el alta regresa a **Pendientes** con el SBC de la fecha de ingreso. ## Fecha límite [#fecha-limite] El plazo es de **5 días hábiles** contados desde la fecha del movimiento. No se cuentan sábados, domingos ni los días de descanso obligatorio del artículo 74 de la Ley Federal del Trabajo. La etiqueta de cada fila cuenta días naturales: **Vence en 7 días** puede ser un plazo de 5 días hábiles. ## La cola [#cola] Las filas se ordenan de la fecha límite más cercana a la más lejana, para que lo urgente quede arriba. * **Vencidos**: movimientos pendientes cuya fecha límite ya pasó. La fila se marca en rojo con la etiqueta **Vencido hace N días**. * **Pendientes**: todo lo que falta por presentar, incluidos los vencidos. * **Presentados**: movimientos confirmados, con la fecha de presentación y el folio del acuse, y las [altas y bajas que ya existían](#existentes) fuera de la plataforma. Primero van las presentaciones más recientes y al final los movimientos que ya existían. Si a un movimiento pendiente le falta un dato que pide el IMSS, la columna **Detalle** dice cuál. Todo movimiento necesita el NSS, el registro patronal y la CURP. Una baja necesita además el motivo de baja. Un alta o una modificación de salario necesita además el RFC, el SBC, el tipo de salario, el tipo de trabajador IMSS y la semana o jornada reducida. Un movimiento pendiente muestra el NSS actual del empleado. Uno presentado muestra el NSS con el que se presentó, aunque después se edite al empleado. ## Generar el archivo para el IDSE [#archivo-idse] El botón **Generar TXT** prepara los archivos que subes al IDSE. Elige el **Movimiento** (alta, baja o modificación de salario) y la plataforma arma un archivo por registro patronal con los movimientos pendientes de ese tipo. Descarga cada uno con **Descargar**. Los movimientos a los que les falta un dato, o cuyo dato no cabe en el formato del IDSE (por ejemplo un NSS que no tiene 11 dígitos), aparecen en **Fuera del archivo** con el motivo. Completa el dato y vuelve a generar el archivo. Generar el archivo no confirma ningún movimiento: después de subirlo al IDSE, confirma cada movimiento como se explica abajo. ## Confirmar la presentación [#confirmar] Después de presentar un movimiento ante el IMSS, abre el menú de su fila y elige **Confirmar presentación**. Puedes capturar el folio del acuse. El movimiento pasa a **Presentados** y no se puede deshacer. **Altas y modificaciones sin SBC.** Si la plataforma no pudo calcular el SBC de un alta o de una modificación de salario, el diálogo te pide el **SBC diario presentado**: captura el que presentaste en el IDSE o el SUA. Es obligatorio y debe ser mayor a cero; la plataforma lo guarda en el movimiento. ## Permisos [#permisos] El permiso **Movimientos IMSS** tiene dos niveles: ver la cola, y confirmar presentaciones y generar los archivos para el IDSE. Los roles que ya podían ver o administrar empleados recibieron el mismo nivel. # Empleados (/docs/employees) La lista de empleados está en **Capital Humano → Empleados**. Desde aquí das de alta, editas, das de baja y reactivas a las personas de tu organización, y abres la información de cada una. ![Lista de empleados en la vista En nómina](/images/guides/employees/employees-list.webp) Usa la búsqueda y **Filtros** para acotar la lista, y **Columnas** para elegir qué datos ver. ## Vistas de la lista [#vistas] Los botones sobre la tabla filtran a las mismas personas de tres formas: * **En nómina**: empleados dados de alta en nómina en al menos una razón social. * **Sin alta en nómina**: empleados con al menos una razón social sin alta en nómina. * **Todos**: todos los empleados, sin importar su estatus. ## Estatus del empleado [#estatus] * **En nómina**: tiene todos los datos que la nómina necesita y se dio de alta en nómina. Solo los empleados en nómina entran al cálculo. * **Sin alta en nómina**: el empleado existe, pero aún no se da de alta en nómina o le falta algún dato. La guía [Alta de empleados](/docs/employees/hiring#dar-de-alta-en-nomina) explica cómo darlo de alta. * **Baja**: la relación laboral terminó. El empleado deja de aparecer en **En nómina** y **Sin alta en nómina**, y puedes reactivarlo después. Si a un empleado en nómina se le borra alguno de los datos que la nómina necesita, regresa a **Sin alta en nómina** por sí solo. ## Columnas [#columnas] De inicio, la tabla muestra **No.**, **No. interno**, **Nombre**, **Razón social**, **Departamento**, **Puesto**, **Estatus**, **Tipo de trabajador** y **Sindicalizado**. **No. interno** solo aparece si tu organización utiliza el número interno. También puedes mostrar **RFC**, **CURP**, **Registro patronal**, **Sucursal**, **Centro de costos**, **Supervisor inmediato**, **Contrato**, **Calendario de nómina**, **Fecha de ingreso**, **Antigüedad**, **Portal** (**Con acceso** o **Sin acceso** al portal de colaborador), **NSS**, **Correo**, **Teléfono**, **Etiquetas** y una columna por cada [campo personalizado](/docs/employees/custom-fields). **Salario diario** y **SBC** solo aparecen para quien puede consultar salarios. Haz clic en el encabezado de una columna para ordenar la lista: el primer clic ordena de forma ascendente, el segundo de forma descendente y el tercero quita el orden. **SBC**, **Etiquetas** y los campos personalizados no se pueden ordenar. Para cambiar el ancho de una columna, arrastra el borde derecho de su encabezado. Doble clic en ese borde le regresa su ancho original. **Columnas**, junto a **Filtros**, abre la lista de columnas en el orden de la tabla: * Marca o desmarca una columna para mostrarla u ocultarla. **Nombre** siempre está visible. * Arrastra la agarradera a la izquierda de una columna para cambiar su lugar en la tabla. * **Restablecer columnas** regresa las columnas visibles, su orden, sus anchos y el orden de la lista a como vienen de inicio. Las columnas, su orden, sus anchos y el orden de la lista se guardan en tu usuario: los encuentras igual la próxima vez que abras la lista. ## Filtros [#filtros] **Filtros** acota la lista por: * **Estatus**, solo en la vista **Todos**: **En nómina** o **Sin alta en nómina**. **Baja** aparece desactivada, porque los empleados dados de baja se consultan en la [lista de bajas](/docs/employees/terminating#lista-de-bajas). * **Razón social**, **Registro patronal**, **Sucursal**, **Departamento**, **Centro de costos**, **Puesto**, **Supervisor inmediato**, **Contrato** y **Calendario de nómina**. * **Tipo de trabajador**, **Tipo de jornada** y **Zona económica**. * **Sindicalizado**: **Sí** o **No**. * **Acceso al portal**: **Con acceso** o **Sin acceso**. * **Etiquetas**. * **Fecha de ingreso**: un rango de fechas en el calendario. Los filtros aplicados aparecen como etiquetas debajo de la barra: haz clic en una para cambiar su valor, quítala con su botón o quítalos todos con **Eliminar filtros**. ## Abrir a un empleado [#abrir] * **Clic en la fila** abre el [panel del empleado](#panel-del-empleado), un resumen rápido sin salir de la lista. * **Clic en el nombre** (subrayado) o en el ícono del ojo abre la [página del empleado](/docs/employees/profile), con toda su información en secciones. ## Acciones de cada empleado [#acciones] El menú de opciones al final de cada fila, o el clic derecho sobre ella, reúne las acciones del empleado: * **Ver detalle** abre la [página del empleado](/docs/employees/profile). * **Vista rápida** abre el [panel del empleado](#panel-del-empleado). * **Ver historial** muestra los cambios a sus datos. * **Editar** abre su formulario completo. * **Dar de baja** aparece mientras el empleado tiene una relación laboral abierta. * **Reactivar** aparece cuando el empleado está dado de baja. ![Menú de opciones de un empleado con Ver detalle, Vista rápida, Ver historial, Editar y Dar de baja](/images/guides/getting-started/employee-actions-menu.webp) ## Panel del empleado [#panel-del-empleado] El clic en la fila, o **Vista rápida**, abre un panel lateral con la información de la persona, sin salir de la lista. Un doble clic en la fila no lo abre, para que puedas seleccionar texto. La barra superior del panel tiene: * Las flechas para pasar al empleado anterior o al siguiente de la lista sin cerrar el panel, con su posición en la lista. Las flechas arriba y abajo del teclado hacen lo mismo. * **Ver perfil**, que abre la [página del empleado](/docs/employees/profile). * El menú de opciones, con las mismas [acciones](#acciones) que la fila. * El botón para cerrar el panel. Debajo aparecen su foto o iniciales, su nombre (que también abre su página), su estatus, la razón social de su relación laboral actual, su número de empleado y su puesto. El panel se organiza en cuatro pestañas y siempre abre en **Resumen**; al pasar a otro empleado conservas la pestaña en la que estás. **Resumen**: * Si el empleado no está dado de alta en nómina, un aviso lista los datos que le faltan, con el enlace **Completar datos**. * **Salario diario** y **SBC**, si puedes consultar salarios, **Último neto pagado** con su nómina y fecha de pago, y la **Antigüedad** con la fecha desde la que cuenta. * **Esta semana**: su asistencia día por día. * **Vacaciones**: los días disponibles y los **Tomados**, **Aprobados** y **Por aprobar**. * Sus etiquetas, que puedes editar desde aquí. * Sus **Notas**, si las tiene. **Personal**: * **Identificación**: RFC, CURP, fecha de nacimiento con su edad, nacionalidad, estado civil y, si tu organización lo utiliza, el número interno. * **Contacto**: correo, teléfono, teléfono secundario y domicilio real. * **Contactos de emergencia** y **Beneficiarios**. * **Campos personalizados**, si tu organización tiene alguno. **Empleo**, de su relación laboral actual: * **Puesto y organización**: razón social, registro patronal, sucursal, departamento, puesto, supervisor inmediato y centro de costos. * **Relación laboral**: tipo de trabajador, contrato, tipo de jornada, fecha de ingreso, fecha de antigüedad (solo subordinados), último día laborado (si ya se dio de baja), si es sindicalizado y si tiene acceso al portal de colaborador. * **Horario**. **Nómina**: * **Salario y prestaciones**: salario diario y SBC (si puedes consultar salarios), tipo de salario, calendario de nómina, zona económica y, para subordinados, NSS y si aporta al IMSS; si aporta, también su tipo de trabajador IMSS y su semana o jornada reducida. * **Datos bancarios**: forma de pago, banco, CLABE interbancaria, número de cuenta y número de tarjeta. * **Créditos**: sus créditos y descuentos. * **Acumulado** del año, de enero al último mes pagado: percepciones, ISR, IMSS trabajador, otras deducciones, **Neto pagado** y **Costo empresa**. El subsidio para el empleo y las percepciones no monetarias aparecen solo si el año los tiene. Los datos que faltan no aparecen como renglones vacíos: cada tarjeta los reúne al final en **Sin capturar**, con el enlace **Completar**, que abre la edición del empleado en la sección donde se capturan. ## En esta sección [#en-esta-seccion] * [Página del empleado](/docs/employees/profile): resumen, datos, asistencia, vacaciones, nómina, expediente e historial de cada persona. * [Alta de empleados](/docs/employees/hiring): el formulario, los datos obligatorios y cómo dejar a un empleado listo para nómina. * [Bajas](/docs/employees/terminating): cómo registrar el último día laborado. * [Reingresos](/docs/employees/rehiring): cómo reactivar a un empleado dado de baja. * [Edición masiva y exportaciones](/docs/employees/bulk-edit): editar muchos empleados a la vez y descargar la lista. * [Créditos y descuentos](/docs/employees/credits): INFONAVIT, FONACOT, préstamos, pensión alimenticia y caja de ahorro. * [Campos personalizados](/docs/employees/custom-fields): datos propios de tu organización en el expediente del empleado. # Página del empleado (/docs/employees/profile) La página del empleado reúne la información de una persona en secciones. Para abrirla, entra a **Capital Humano → Empleados** y haz clic en su nombre, que aparece subrayado, en el ícono del ojo de su fila o en **Ver detalle** del menú de la fila. Desde el panel de consulta rápida, **Ver detalle** de su menú de opciones también la abre. El clic en el resto de la fila abre el [panel de consulta rápida](/docs/employees#panel-del-empleado). ## Encabezado [#encabezado] Muestra la foto, el nombre, el estatus y el número de empleado. Desde ahí tienes tres acciones: * **Ver historial** abre los cambios registrados a sus datos. * **Dar de baja**, mientras tiene una relación laboral abierta. La guía [Bajas](/docs/employees/terminating) explica el diálogo. * **Reactivar**, cuando está dado de baja. La guía [Reingresos](/docs/employees/rehiring) explica el diálogo. ## Resumen [#resumen] Es la sección que se abre primero. Si el empleado no está dado de alta en nómina, un aviso te lo indica arriba. Después muestra: * **Último neto pagado**, **Salario diario**, los retardos del mes y la **Antigüedad**. * **Neto pagado por mes** de los últimos 12 meses y el **Acumulado del ejercicio**: percepciones, ISR, IMSS obrero, vales de despensa entregados (cuando los hay), otras deducciones y neto pagado. El renglón de subsidio para el empleo aparece solo si algún recibo del año lo pagó como percepción. * **Datos del empleado**: razón social, puesto, departamento, jefe directo, fecha de ingreso, contrato, calendario de nómina y contacto, entre otros. * **Esta semana**: su asistencia día por día. * **Vacaciones**: su saldo. Los montos solo se muestran a quien puede consultar salarios. ## General [#general] Los datos del empleado agrupados en Personal, Organización, Fiscal e IMSS, Datos bancarios, Contratación, SDI/SBC, Adicional y Notas. **Editar**, en cada grupo, abre el [formulario del empleado](/docs/employees/hiring#editar) con la sección que contiene esos datos abierta: Fiscal e IMSS y Contratación abren **Alta a nómina**, y Adicional abre **Campos adicionales**. Al guardar o cancelar regresas a esta sección. Aquí también se registran dos listas que no están en el formulario: * **Contactos de emergencia**: nombre, parentesco y teléfono de a quién avisar. * **Beneficiarios**: nombre, parentesco y porcentaje, para el finiquito y el seguro de vida. Los porcentajes de todos sus beneficiarios no pueden sumar más de 100%. Haz clic en **Agregar** para capturar uno nuevo; cada registro tiene **Editar** y **Eliminar**. ## Asistencia [#asistencia] Un calendario del mes con cada día coloreado según su estatus. Con las flechas pasas al mes anterior o al siguiente. * Los indicadores del mes muestran los **Días con checada**, los **Retardos** (días con un retardo registrado), las **Incompletas** (días laborables a los que les faltan checadas), las **Faltas** registradas en incidencias y las **Correcciones pendientes**. * El tiempo libre capturado, como vacaciones, un permiso o una incapacidad, gana sobre las checadas: el día se colorea como tiempo libre aunque tenga checadas. Los días de tiempo libre, los descansos y los festivos nunca cuentan como incompletos. * Al elegir un día ves sus checadas, incidencias y correcciones. Si tiene checadas, **Abrir checador** te lleva al [reloj checador](/docs/attendance/time-clock). * **Ver incidencias** abre las [incidencias](/docs/attendance/incidences) del empleado en ese mes. ## Vacaciones [#vacaciones] La tarjeta de saldo muestra los días **Disponibles**, los que están **Por vencer**, los **Tomados**, los **Aprobados por tomar** y los que están **En autorización**, junto con el periodo actual y el siguiente aniversario. Solo el personal subordinado con una relación laboral vigente acumula vacaciones. Debajo, la tabla tiene dos vistas: * **Solicitudes**: las vacaciones que pidió desde el [portal de colaborador](/docs/employee-portal). * **Movimientos del saldo**: los otorgamientos por aniversario y los días usados. ## Nómina [#nomina] Elige el ejercicio con el selector de año. La sección tiene tres vistas: * **Recibos**: un recibo por cada nómina cerrada, con el estatus de su CFDI. Desde el menú de cada recibo puedes **Ver recibo**, **Descargar PDF** o **Descargar XML**. * **Acumulados**: lo acumulado en el ejercicio, sumado de sus nóminas cerradas. * **Créditos**: sus créditos INFONAVIT, FONACOT, préstamos y pensiones. La guía [Créditos y descuentos](/docs/employees/credits) explica cómo registrarlos. ## Finiquitos [#finiquitos] Los escenarios de finiquito o liquidación que se han calculado para el empleado, del de fecha de baja más reciente al más antiguo. La sección solo aparece si tienes el permiso para ver las corridas de nómina. * La tabla muestra la **Referencia** de cada escenario, con la etiqueta **Complemento** cuando lo es, el **Motivo de baja**, la **Fecha de baja**, el **Estatus** y, si puedes ver sueldos, el **Neto**. Usa **Buscar referencia...** para encontrar uno. * El menú de opciones de cada escenario, o el clic derecho, tiene las mismas acciones que en **Nómina → Finiquitos**, entre ellas descargar sus cartas en PDF. Un escenario cancelado no tiene cartas para descargar. La guía [Finiquito y liquidación](/docs/terminations#cartas) explica qué carta ofrece cada escenario. * El botón **Renuncia** abre la ventana **Carta de renuncia**, te pide la **Fecha de baja** que se imprime en la carta y la descarga en PDF con la plantilla de la razón social del empleado. Sirve aunque el empleado no tenga escenarios, y aparece si también puedes ver sueldos. La fecha de baja no puede ser anterior a la fecha de ingreso del empleado. Si el empleado no tiene escenarios, la sección muestra **No se encontraron finiquitos para este empleado.** con la indicación **Calcula su finiquito o liquidación en Nómina → Finiquitos.** ## Expediente [#expediente] Los documentos del empleado, organizados en carpetas. Cada empleado tiene desde el inicio las carpetas **Contratos**, **Identificación**, **IMSS**, **Fiscal**, **Nómina** y **Otros**. A la izquierda está el árbol de carpetas y a la derecha los archivos de la carpeta que eliges. **Crear una carpeta.** Haz clic en **Nueva carpeta**. Si tienes una carpeta seleccionada, la nueva se crea dentro de ella; si no, en la raíz del expediente. Para quitar la selección presiona **Esc** o haz clic en un espacio vacío del árbol. **Subir archivos.** 1. Abre la carpeta donde quieres guardarlos. 2. Haz clic en **Subir archivos** y elige uno o varios. Se aceptan PDF, imágenes, Word y Excel de hasta 20 MB cada uno. 3. Si quieres, activa **Visible al colaborador**. 4. Haz clic en **Subir archivos** para confirmar. Para subir archivos el expediente necesita al menos una carpeta. **Organizar.** El menú de opciones de cada archivo, o el clic derecho sobre él, tiene **Descargar**, **Renombrar**, **Mover a…** y **Eliminar**; el interruptor **Visible al colaborador** de su fila lo cambia en cualquier momento. Las carpetas se renombran o eliminan desde su propio menú. Eliminar una carpeta borra también sus subcarpetas y sus archivos, y no se puede deshacer. Usa **Buscar archivo** para filtrar los archivos de la carpeta abierta. ## Historial [#historial] Todos los cambios del empleado en una sola lista: los cambios a sus datos, hechos desde el formulario, la edición masiva o la API; los cambios de salario y SBC; y sus movimientos ante el IMSS. Filtra con **Todo**, **Cambios de datos**, **Salario y SBC** o **Movimientos IMSS**, y usa la búsqueda para encontrar un campo, un valor o un usuario. # Reingresos (/docs/employees/rehiring) Cuando una persona que ya trabajó en la organización vuelve, no se captura de nuevo: se reactiva su registro. El reingreso abre una relación laboral nueva y conserva sus datos personales, su RFC, su CURP y sus datos bancarios. ## Reactivar a un empleado [#reactivar] 1. Busca al empleado en **Configuración → Capital Humano → Bajas**, o en la vista **Todos** de **Capital Humano → Empleados**. 2. Abre el menú de opciones de su fila y elige **Reactivar**. También puedes hacerlo con **Reactivar** en el encabezado de su [página](/docs/employees/profile#encabezado). 3. En **Razón social de reingreso**, elige la razón social con la que vuelve. Por omisión es la de su última baja, pero puede ser otra. 4. Elige la **Fecha de reingreso**. Por omisión es el día de hoy. 5. Decide si **Mantener antigüedad**: * Activado, se conserva su fecha de antigüedad original. * Desactivado, la antigüedad se reinicia a la fecha de reingreso. 6. Si el empleado aporta al IMSS, responde **Alta existente IMSS**: **Sí** si el alta del reingreso ya se presentó ante el IMSS fuera de la plataforma, **No** si todavía hay que presentarla. Es obligatoria. 7. Si el empleado aporta al IMSS, revisa el **Tipo de trabajador IMSS** y la **Semana o jornada reducida**. Vienen con los del empleo anterior y son obligatorios. 8. Haz clic en **Reactivar**. ![Diálogo Reactivar empleado con razón social, fecha de reingreso y Mantener antigüedad](/images/guides/employees/reactivate-dialog.webp) El reingreso crea un alta en [Movimientos IMSS](/docs/employees/imss-movements): pendiente con su fecha límite si respondiste **No**, o registrada como ya presentada si respondiste **Sí**. La antigüedad determina, entre otras cosas, los días de vacaciones que le corresponden según su contrato. Si la fecha de reingreso cae dentro de un periodo de nómina que ya se cerró, la plataforma te pide confirmar el cambio retroactivo, igual que en un [alta con fecha pasada](/docs/employees/hiring#periodo-cerrado). ## Después del reingreso [#despues-del-reingreso] La relación laboral nueva no copia los datos de nómina de la anterior. Abre al empleado con **Editar** y revisa, antes de su siguiente nómina: * Puesto, supervisor inmediato, sucursal, departamento y centro de costos, en la sección **Organización**. * Si aporta al IMSS, el **Registro patronal**, en **Registro**. * En **Alta a nómina**: el sueldo, el **Calendario de nómina**, el **Contrato**, el **Régimen fiscal (SAT)** y, si aporta al IMSS, el **NSS (IMSS)**. Guarda los cambios con **Dar de alta en nómina** marcada. Si falta alguno de esos datos, un aviso te dice cuáles y el formulario te lleva al primero. # Bajas (/docs/employees/terminating) Dar de baja a un empleado cierra su relación laboral con la razón social. A partir de ese momento deja de aparecer en las vistas **En nómina** y **Sin alta en nómina**, deja de entrar a las nóminas que abras y pierde el acceso al portal de colaborador. ## Dar de baja a un empleado [#dar-de-baja] 1. Entra a **Capital Humano → Empleados**. 2. En la fila del empleado, abre el menú de opciones y elige **Dar de baja**. La opción también está en el menú del panel del empleado y en el encabezado de su [página](/docs/employees/profile#encabezado). 3. Revisa los datos de la relación laboral: razón social, fecha de ingreso, fecha de antigüedad y número del IMSS. 4. Elige el **Último día laborado**. Por omisión es el día de hoy. 5. Si el empleado aporta al IMSS, responde **Baja existente IMSS**: **Sí** si la baja ante el IMSS ya se presentó fuera de la plataforma, **No** si todavía hay que presentarla. Es obligatoria. 6. Haz clic en **Dar de baja**. ![Diálogo Dar de baja con la razón social y el último día laborado](/images/guides/employees/terminate-dialog.webp) El pie del diálogo te recuerda que se le paga hasta su último día laborado, incluido. En este paso solo se registra la fecha. No se captura el motivo de la baja ni se calcula el finiquito: eso se hace en **Nómina → Finiquitos**, y al confirmar un finiquito el empleado también queda dado de baja. La guía [Finiquito y liquidación](/docs/terminations#escenarios) explica qué se paga según el motivo y cómo se calcula. ## Baja en un periodo ya cerrado [#periodo-cerrado] Si el último día laborado cae dentro de un periodo de nómina que ya se cerró, el empleado cobró días que no trabajó. Antes de registrar la baja, la plataforma te pide confirmar el cambio retroactivo y te muestra los periodos afectados. Al confirmar, lo ya pagado no se modifica: la diferencia se registra como un ajuste retroactivo que se descuenta en la siguiente nómina, donde puedes revisarlo o cancelarlo antes del cierre. **Movimiento ante el IMSS.** Si el empleado cotiza al IMSS, la baja crea un movimiento en [Movimientos IMSS](/docs/employees/imss-movements). Con **Baja existente IMSS** en **No**, queda pendiente con su fecha límite de presentación; con **Sí**, queda registrada como ya presentada. ## Lista de bajas [#lista-de-bajas] Los empleados dados de baja se consultan en **Configuración → Capital Humano → Bajas**. La lista tiene la misma búsqueda, los mismos filtros (más **Fecha de baja**) y el mismo menú **Columnas** que la de empleados. El estatus de cada persona aparece como **Baja**. Desde el menú de cada fila puedes abrir su detalle o [reactivarlo](/docs/employees/rehiring). ![Lista de bajas con un empleado en estatus Baja](/images/guides/employees/terminations-list.webp) También los encuentras en la vista **Todos** de la lista de empleados. ## Bajas desde la edición masiva [#edicion-masiva] En la [edición masiva](/docs/employees/bulk-edit) puedes dar de baja a varios empleados a la vez: captura la fecha en la columna **Último día laborado** de cada uno, responde **Baja existente IMSS** en la columna de al lado si el empleado aporta al IMSS, y publica el borrador. El diálogo de publicación te muestra cuántos empleados se darán de baja antes de confirmar. # Primeros pasos (/docs/getting-started) Esta guía te lleva de una organización recién creada a tu primera nómina cerrada. Empieza con la **configuración inicial**, que la plataforma te presenta en cuanto entras, y sigue con la lista **Primeros pasos** del menú lateral. **Antes de empezar.** Necesitas un usuario con acceso de administración en la organización. Ten a la mano la constancia de situación fiscal de la empresa y los datos del registro patronal ante el IMSS. ## Configuración inicial [#configuracion-inicial] Una organización nueva abre directamente en la configuración inicial. Mientras no la termines, el resto de la plataforma no está disponible: cualquier sección te regresa aquí. Empieza con un video de bienvenida; haz clic en **Comenzar configuración** cuando quieras continuar. Después vienen seis pasos, uno por pantalla: 1. [Razón social](#razon-social) 2. [Registro patronal](#registro-patronal), opcional si solo pagas a asimilados a salarios 3. [Estructura](#estructura) 4. [Calendario de nómina](#calendario-de-nomina) 5. [Horario](#horario) 6. [Invitar al equipo](#invitar-al-equipo), opcional Al terminar, la organización tiene todo lo que pide el alta de un empleado: cada lista del formulario ya tiene al menos una opción. La barra de arriba muestra en qué paso vas; haz clic en un segmento para ir a ese paso. Un paso que depende de otro no se puede abrir hasta terminar el anterior; pasa el cursor sobre su segmento para ver cuál falta. Utiliza **Continuar** para guardar y avanzar, y **Anterior** para regresar. Si sales a la mitad, la plataforma te devuelve al paso donde te quedaste, con lo que ya capturaste. Cada paso tiene un enlace **Consultar la guía** que te trae a la sección correspondiente de esta página. Al terminar el último paso verás una pantalla de felicitación con el resumen de los pasos. Haz clic en **Dashboard** para entrar a la plataforma. Si todavía falta un paso obligatorio, la plataforma te lleva a ese paso en lugar de terminar. ## Razón social [#razon-social] La razón social es la empresa que emite la nómina ante el SAT. Los empleados, los calendarios y los registros patronales pertenecen a una razón social, y una organización puede tener varias. En este paso registras una; las demás se agregan después en **Configuración → Organización → Razón social**. Captura el **Nombre** con el que la reconoces dentro de la plataforma y su identidad fiscal, que se describe en la siguiente sección. Todos los datos de este paso son obligatorios. Al crear la primera razón social, la plataforma le asigna el **Contrato base (indeterminado)**, con las prestaciones mínimas de ley, junto con el catálogo de conceptos de nómina, así que no hay que configurar prestaciones ni contratos para calcular la primera nómina. La sección [Prestaciones y contrato](#esquema-de-prestaciones) explica qué incluyen. ## Identidad fiscal [#identidad-fiscal] Cada razón social activa necesita los datos del emisor del recibo: * **Razón social fiscal**, tal como aparece en la constancia de situación fiscal. * **RFC**. * **CURP**, solo si la razón social es una persona física (RFC de 13 caracteres). * **Código postal** del domicilio fiscal. * **Régimen fiscal (SAT)**, por ejemplo 601, General de Ley Personas Morales. * **Certificado de sello digital (CSD)**: el archivo del certificado (.cer), la llave privada (.key) y su contraseña. Todos menos el CSD se capturan en la configuración inicial, donde también puedes escribir la **Ciudad** de la razón social (opcional): no forma parte de la identidad fiscal y las cartas de baja la usan en su línea de ciudad y fecha. El CSD se sube después, desde **Configuración → Organización → Razón social**, en el menú de opciones de la fila: **Certificado de sello digital**. La llave y la contraseña se guardan cifradas y no se vuelven a mostrar. Lo necesitas para timbrar los recibos, no para calcular la nómina. ![Diálogo para subir el certificado de sello digital](/images/guides/configuration/legal-entity-csd-dialog.webp) La columna **Identidad fiscal** de la tabla de razones sociales indica **Completa** cuando no falta nada. Si dice **Incompleta**, pasa el cursor sobre ella para ver qué dato falta. ![Tabla de razones sociales con la identidad fiscal completa](/images/guides/configuration/legal-entities-table.webp) ## Registro patronal [#registro-patronal] El registro patronal del IMSS determina las cuotas obrero-patronales de los empleados que cotizan. En este paso registras el principal; si la empresa tiene más, los demás se agregan después en **Configuración → Organización → Registros patronales**. Si tu organización solo paga a asimilados a salarios, que no cotizan al IMSS, deja el formulario vacío y haz clic en **Continuar**: el paso se omite. En cuanto capturas algún dato, **Continuar** lo guarda. 1. La **Razón social** es la del paso anterior. 2. Captura el **Número de registro** y, si quieres, una **Etiqueta** para reconocerlo (por ejemplo, la sucursal). 3. Selecciona la **Clase de riesgo IMSS** y escribe la **Prima de riesgo** en porcentaje, tal como la publica el IMSS: una prima de 0.54355 % se escribe **0.54355**. 4. Indica desde cuándo está **Vigente** esa prima. Si regresas a este paso después de guardarlo, la razón social ya no se puede cambiar: el registro patronal queda ligado a ella. ## Estructura [#estructura] El puesto y el departamento son las listas con las que se registra a cada empleado. En este paso das de alta uno de cada uno, solo con su nombre. La sucursal y el centro de costos son opcionales: captúralos si tu organización los utiliza. La clave de cada uno se genera a partir del nombre. Los demás, y el resto de sus datos (como la banda salarial de un puesto o la jerarquía de departamentos y sucursales), se capturan después en **Configuración → Organización**. Si regresas a este paso, editas los que ya diste de alta en lugar de crear otros. ## Calendario de nómina [#calendario-de-nomina] El calendario define cada cuánto se paga y genera los periodos del año. Cada empleado se paga con el calendario que tenga asignado. En este paso creas el calendario con el que se paga a la mayoría de los empleados; los demás se agregan después en **Configuración → Nómina → Calendarios**. 1. Escribe el **Nombre del calendario** y elige la **Periodicidad**: diaria, semanal, decenal, catorcenal, quincenal o mensual. 2. Elige la **Tabla de ISR** con la que se retiene el impuesto: la de la misma periodicidad, o la mensual acumulada. 3. Decide si dejas marcada la casilla **Aplicar descuento de séptimo día proporcional**. Viene marcada: las faltas y ausencias sin goce descuentan además un sexto del día de descanso. Desmárcala si tu empresa paga el día de descanso completo; consulta [El séptimo día proporcional](/docs/configuration/calendars#septimo-dia). 4. Elige los **Días del mes**: 30 o 30.4. Con ellos se convierte el sueldo mensual a diario y se paga una nómina mensual; consulta [Los días del mes](/docs/configuration/calendars#dias-del-mes). 5. Revisa el **Año** y los **Días para pago**, que se cuentan a partir del cierre de cada periodo; un valor negativo paga antes del cierre. En un calendario semanal o catorcenal puedes elegir el **Inicio del primer periodo**. Consulta [Calendarios de nómina](/docs/configuration/calendars#dias-para-pago). El calendario se crea para todas las razones sociales de la organización. Al guardar se generan todos los periodos del año. Una vez creado, la periodicidad ya no se puede cambiar; el nombre, los días para pago, la tabla de ISR, los días del mes y el séptimo día sí, y son lo único que muestra el paso si regresas a él. Los periodos de otros años se generan desde el detalle del calendario. ## Prestaciones y contrato [#esquema-de-prestaciones] No forman parte de la configuración inicial: la razón social ya tiene asignado el **Contrato base (indeterminado)**, así que la nómina se puede calcular desde el primer día. Las prestaciones del empleado (tabla de aguinaldo, tabla de vacaciones, prima vacacional y prima dominical) se configuran en su contrato. El contrato base se crea con los mínimos que marca la Ley Federal del Trabajo: aguinaldo de 15 días, prima vacacional de 25 % y vacaciones según la tabla de ley, y un contrato nuevo empieza igual. ### Contrato [#contrato] El contrato registra el tipo de relación laboral ante el SAT y los conceptos extralegales que se le asignan al empleado. Revísalo en **Configuración → Nómina → Contratos**. Si necesitas otro tipo de contrato, como uno por tiempo determinado, agrégalo desde ahí. ![Tabla de contratos con el contrato base](/images/guides/getting-started/contracts-table.webp) ## Horario [#horario] El horario predeterminado aplica a todo empleado que no tenga otro asignado. El paso viene prellenado de lunes a viernes, de 9:00 a 18:00; ajusta los días, la entrada, la salida, la tolerancia y, si aplica, la comida. Al continuar, se guarda como el horario predeterminado de la organización. Los horarios adicionales, como los rotativos, se agregan después en **Configuración → Asistencia → Horarios**. ## Invitar al equipo [#invitar-al-equipo] Este paso es opcional. Escribe el **Correo electrónico** de la persona, elige su **Rol** y haz clic en **Enviar invitación**; pasa el cursor sobre un rol para ver qué puede hacer en cada sección. Puedes enviar varias invitaciones antes de continuar, o ninguna. Las que siguen pendientes aparecen debajo, en **Invitaciones enviadas**; desde el menú de opciones de cada una puedes **Reenviar invitación** o **Cancelar invitación**. Las invitaciones también se envían después desde **Configuración → Usuarios y permisos**. La guía [Usuarios y permisos](/docs/configuration/access#invitar) explica cómo funcionan. ## La lista Primeros pasos [#lista-primeros-pasos] Al terminar la configuración inicial, el menú lateral muestra la tarjeta **Primeros pasos** con lo que falta para operar la nómina. Haz clic en ella para abrir la lista: 1. **Configuración inicial**, ya completa. 2. **Registrar primer empleado**. 3. **Dar de alta en nómina**: el empleado está activo y tiene completos sus datos obligatorios. 4. **Invitar al equipo**, si no lo hiciste en la configuración inicial. Cada paso pendiente tiene un botón que lleva a su pantalla, y la lista se actualiza sola conforme avanzas. Si no la necesitas, utiliza **Ocultar** al pie de la lista; mientras no esté completa, puedes mostrarla de nuevo desde el menú de tu usuario con **Mostrar primeros pasos**. ## Primer empleado [#primer-empleado] La primera vez que entras a la plataforma, un recorrido guiado te lleva a **Capital Humano → Empleados** y te muestra cómo registrar al primer empleado. Puedes seguirlo hasta el final u **Omitir** en cualquier momento; en ambos casos ya no vuelve a aparecer. El alta se hace en **Capital Humano → Empleados**, con el botón **Empleado**. El formulario tiene una sección fija, **Registro**, y debajo las demás secciones, cada una con una casilla: solo se capturan y se guardan las que marcas. La guía [Alta de empleados](/docs/employees/hiring#secciones) explica cada una. ![Sección Registro del alta de empleado](/images/guides/getting-started/employee-form-registration.webp) **Registro.** Captura el **Nombre**, el **Apellido paterno**, el **RFC**, la **CURP**, la **Razón social**, el **Tipo de trabajador** (**Subordinado**, **Asimilado a salarios** u **Honorarios**), la **Fecha de ingreso** y la **Entidad del centro de trabajo**, que es el estado donde labora y determina el impuesto sobre nómina. El formulario oculta los campos que no aplican a cada tipo de trabajador. Si **Aporta al IMSS**, elige el **Registro patronal** y responde **Alta existente IMSS**: **Sí** si su alta ante el IMSS ya se presentó fuera de la plataforma, **No** si todavía hay que presentarla y quieres que aparezca en [Movimientos IMSS](/docs/employees/imss-movements). Si le das **Acceso al portal de colaborador**, elige su **Método de acceso**: con **Correo** captura su **Correo electrónico**; con **Teléfono**, para quien no tiene correo, captura su **Teléfono** y entra con un código por WhatsApp. **Alta a nómina.** Marca la sección y captura el **Sueldo bruto** con su periodo, el **Calendario de nómina**, el **Contrato**, el **Tipo de jornada**, el **Régimen fiscal (SAT)**, que para un asalariado es 02, Sueldos y salarios, el **NSS (IMSS)**, el **Tipo de trabajador IMSS** y la **Semana o jornada reducida** si aporta al IMSS, y el **Código postal** del recibo. Si su Constancia de Situación Fiscal trae otro domicilio, marca **Domicilio fiscal distinto al domicilio** y captura el **Código postal fiscal**. ![Sección Alta a nómina con la casilla Dar de alta en nómina](/images/guides/getting-started/employee-form-payroll.webp) **Organización.** Si la marcas, elige el **Puesto** y el **Departamento** que diste de alta en la configuración inicial y, si las utilizas, la **Sucursal** y el **Centro de costos**. El supervisor es opcional. **Horario.** Es opcional; si no marcas la sección, aplica el horario predeterminado de la organización. Haz clic en **Crear empleado**. Si todavía no tienes todos los datos, puedes guardarlo así: queda como **Sin alta en nómina** hasta que lo des de alta. ## Empleado listo para nómina [#empleado-listo-para-nomina] Un empleado entra al cálculo solo cuando está **En nómina**. Para darlo de alta, marca **Dar de alta en nómina**, dentro de la sección **Alta a nómina**, al crearlo o al editarlo. Si falta algún dato, al guardar un aviso te dice cuáles y el formulario te lleva al primero. Los datos que se revisan son: razón social, RFC, CURP, código postal, entidad del centro de trabajo, sueldo, régimen fiscal, tipo de jornada, calendario de nómina, contrato y, si aporta al IMSS, su NSS, su tipo de trabajador IMSS y su semana o jornada reducida. En la lista de empleados, la vista **Sin alta en nómina** muestra a quienes aún no están dados de alta. Si a un empleado en nómina se le borra alguno de esos datos, regresa a **Sin alta en nómina** por sí solo; para darlo de alta de nuevo, completa el dato y marca otra vez la casilla. ![Vista Sin alta en nómina de la lista de empleados](/images/guides/getting-started/employees-list-pending.webp) ## Primera nómina calculada [#primera-nomina-calculada] 1. Entra a **Nómina → Cálculo** y haz clic en **Ver calendario** en tu calendario. 2. En el periodo actual, haz clic en **Abrir periodo**. Se crea la corrida del periodo con los empleados activos de ese calendario. 3. Si hay incidencias por capturar (faltas, horas extra, incapacidades), captúralas con el botón **Incidencias** de la corrida antes de calcular. 4. Haz clic en **Calcular nómina**. ![Calendario con el periodo actual listo para abrirse](/images/guides/getting-started/calendar-open-period.webp) La corrida muestra lo que reciben los trabajadores (percepciones, deducciones y neto a depositar) y lo que le cuesta a la empresa, incluida la carga patronal. ![Corrida calculada con el neto a depositar y el costo para la empresa](/images/guides/getting-started/run-calculated.webp) Abre el recibo de cualquier empleado para revisarlo. La pestaña **Cálculo paso a paso** muestra cómo se llegó a cada importe: las bases, el sueldo del periodo, el ISR, las cuotas del IMSS y el neto. ![Recibo de nómina de una empleada](/images/guides/getting-started/receipt-summary.webp) ![Pestaña Cálculo paso a paso del recibo](/images/guides/getting-started/receipt-walkthrough.webp) Mientras el periodo siga abierto puedes corregir datos o capturar incidencias y usar **Recalcular** las veces que haga falta. ## Primera nómina cerrada [#primera-nomina-cerrada] Cuando los recibos estén correctos, cierra el periodo desde **Opciones → Cerrar periodo**. El diálogo resume el periodo, los empleados y el neto a depositar; escribe **CERRAR** para confirmar. ![Diálogo para cerrar el periodo de nómina](/images/guides/getting-started/close-period-dialog.webp) **El cierre es definitivo.** Un periodo cerrado no se puede recalcular ni reabrir. Si después cambia algo que afecta ese periodo, como una incidencia o una baja con fecha dentro de él, la diferencia se aplica como un ajuste retroactivo en la siguiente nómina. ![Corrida cerrada](/images/guides/getting-started/run-closed.webp) Con esto tienes tu primera nómina cerrada. A partir de aquí, cada periodo se opera igual: abrir, capturar incidencias, calcular, revisar y cerrar. La guía [Operar la nómina](/docs/payroll) explica cada paso con más detalle. # Solicitudes (/docs/requests) Los colaboradores piden desde el portal de colaborador vacaciones y permisos, la corrección de una checada que falta y la justificación de un retardo. Todas llegan a **Incidencias → Solicitudes**, el primer elemento del grupo Incidencias del menú lateral, junto con las horas extra que esperan autorización. El número junto al nombre indica cuántas siguen pendientes. ## Qué tipos de solicitud hay [#tipos] | Tipo | Qué pide el colaborador | Quién la resuelve | Al aprobarla | | ------------------------------ | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | **Vacaciones** y **Permisos** | Días de vacaciones o un permiso con o sin goce de sueldo. | Su jefe directo y, si la organización lo pide, Recursos Humanos después. Si no tiene jefe directo, solo Recursos Humanos. | Se registran las incidencias de esos días y, en vacaciones, se descuentan del saldo. | | **Correcciones de checada** | Agregar una checada que falta, con su hora y su motivo. | Su jefe directo o Recursos Humanos, quien actúe primero. | La checada se agrega a su asistencia con el origen **Corrección aprobada** y los retardos del día se vuelven a calcular. | | **Justificaciones de retardo** | Justificar un retardo que la plataforma derivó de sus checadas. | Su jefe directo o Recursos Humanos, quien actúe primero. | El retardo se elimina de la asistencia y la plataforma no lo vuelve a derivar. | | **Horas extra** | No las pide el colaborador: son las horas extra dobles o triples capturadas que esperan autorización. | Los niveles encendidos en la configuración de asistencia: su jefe directo, Recursos Humanos o los dos, en ese orden. Recursos Humanos decide también el paso del jefe directo desde aquí. | Se pagan en la nómina del periodo. | La guía [Lo que ve el colaborador](/docs/employee-portal/worker#solicitudes) explica cómo se envía cada una, y [Lo que ve el supervisor](/docs/employee-portal/supervisor#solicitudes) cómo las resuelve el jefe directo desde el portal. ## La lista de solicitudes [#lista] Arriba, cinco tarjetas muestran cuántas solicitudes pendientes hay de cada tipo. Un clic en una tarjeta filtra la lista por ese tipo; otro clic elimina el filtro. La lista muestra cada solicitud con su **Tipo**, el **Empleado**, el **Detalle**, el **Avance** y cuándo fue **Enviada**. El **Avance** dice en qué paso va, por ejemplo **Espera al jefe directo**, **Espera a RH** o **Jefe directo aprobó**. Al entrar, la lista muestra las solicitudes pendientes. Usa la búsqueda para encontrar a un empleado y **Filtros** para acotar por **Estatus**, **Tipo**, **Razón social** o **Fecha**. Haz clic en una solicitud para abrir su detalle: los días solicitados y el saldo de vacaciones, o las checadas de ese día junto con la checada solicitada o el retardo que se quiere justificar, además del motivo del colaborador y el historial de la aprobación. El detalle de las horas extra se describe en [Horas extra](#horas-extra). ## Resolver una solicitud [#resolver] Desde el detalle o desde el menú de cada solicitud: * **Aprobar** aplica la solicitud. La nota es opcional. * **Rechazar** no cambia nada en la asistencia, y la nota es obligatoria: el colaborador la recibe como explicación. Al resolver una solicitud del portal, el colaborador recibe un aviso con la decisión. En las correcciones y justificaciones, el diálogo muestra el motivo del colaborador y lo que va a pasar al aprobar. Si el día pertenece a un periodo de nómina ya cerrado, la plataforma pide confirmar el ajuste retroactivo antes de aprobar. Un retardo que ya se pagó en una nómina cerrada no se puede justificar, y si el retardo ya no existe al resolver, la solicitud se cancela sola. ## Horas extra [#horas-extra] Las horas extra dobles y triples quedan pendientes de autorizar hasta que las aprueban todos los niveles encendidos en **Configuración → Asistencia → Reloj checador**, en **Autorización de horas extra**. La guía [Incidencias y conceptos](/docs/attendance/incidences#autorizacion-horas-extra) explica los niveles y qué se aprueba solo al capturar. El detalle muestra: * Las horas, el tipo, el día, quién las capturó y sus notas. * Las **Checadas del día**, con las checadas ligadas a las horas extra marcadas. * **Autorización**: el historial de cada revisión, con quién aprobó o rechazó cada nivel, cuándo y su nota. Si las horas o el tipo se editaron antes de terminar, arriba aparece la **Revisión vigente** y debajo la **Revisión anterior**, con los niveles que quedaron sin decidir marcados como **Se editaron las horas antes de decidir**. Puedes **Aprobar** o **Rechazar** mientras estén pendientes, aunque esperen al jefe directo, que las decide desde el portal de colaborador: quien actúe primero resuelve ese paso. Al aprobar, también se aprueba el paso del jefe directo si seguía pendiente, y el historial lo registra como **RH aprobó por Jefe directo**. Rechazar pide una nota, que queda en el historial; las horas extra rechazadas no se pagan. Si el día pertenece a un periodo de nómina ya cerrado, la plataforma pide confirmar el ajuste retroactivo antes de aprobar. ## Tiempo libre: omitir al jefe directo y revertir [#tiempo-libre] * Mientras una solicitud de tiempo libre espera al jefe directo, **Omitir jefe directo** la pasa a Recursos Humanos para que la decidas tú. * **Revertir aprobación**, en una solicitud de tiempo libre aprobada, la cancela, borra sus incidencias y devuelve los días al saldo de vacaciones. Las correcciones de checada pendientes también aparecen en la pestaña **Resumen** de **Incidencias → Asistencia**, en el detalle del día y en el indicador **Correcciones pendientes**. Consulta [Resumen de asistencia](/docs/attendance/summary#dia). # Cierre y ajustes retroactivos (/docs/payroll/closing) Cerrar el periodo es el último paso del ciclo. Después del cierre, lo pagado en ese periodo ya no cambia: cualquier corrección posterior se aplica como un ajuste retroactivo en la siguiente nómina. ## Cerrar el periodo [#cerrar-el-periodo] Para cerrar, la corrida debe estar en **Calculado**. 1. En la corrida, abre **Opciones** y elige **Cerrar periodo**. 2. El diálogo **Cerrar periodo de nómina** resume el periodo, los empleados en la corrida y el neto a depositar. 3. Escribe **CERRAR** en el campo de confirmación. 4. Haz clic en **Cerrar periodo**. ![Menú Opciones de la corrida con Cerrar periodo](/images/guides/payroll/run-options-menu.webp) ![Diálogo para cerrar el periodo de nómina](/images/guides/getting-started/close-period-dialog.webp) **El cierre es definitivo.** Un periodo cerrado no se puede recalcular ni reabrir. Revisa los recibos antes de confirmar. Si quedan horas extra del periodo sin autorizar, el diálogo lo avisa con cuántos registros siguen pendientes. El aviso no impide cerrar: esas horas no se pagan en este periodo y, si se autorizan después, entran como ajuste retroactivo en la siguiente nómina. Consulta [Horas extra por autorizar](/docs/payroll/run#horas-extra-por-autorizar). Si quedan días incompletos en el periodo, el diálogo también lo avisa, con cuántos empleados y días, y **Ver checadas** te lleva a revisarlos. El aviso no impide cerrar: esos días se pagan como trabajados y una falta capturada después entra como ajuste retroactivo. Consulta [Días incompletos](/docs/payroll/run#dias-incompletos). Si aparece el mensaje **El cálculo no está al día**, hay cambios de incidencias o ajustes retroactivos posteriores al último cálculo. Recalcula la nómina y vuelve a cerrar. Lo mismo pasa si la corrida muestra la nota **Sincronización pendiente**. Al cerrar, la corrida pasa a **Cerrado** y la barra de pasos marca los tres pasos como completos. Las fechas del periodo quedan bloqueadas. Al cerrar un periodo ordinario, la plataforma también descuenta del saldo de vacaciones los días que pagó y que capturaste directamente como incidencia. Los días de solicitudes aprobadas no se descuentan otra vez, porque ya se restaron al aprobarse. Si cambias un día de vacaciones capturado directamente en un periodo que ya estaba cerrado (lo capturas tarde, lo editas o lo eliminas), el saldo se mueve cuando se cierra la nómina que paga su ajuste retroactivo. Las nóminas extraordinarias, como aguinaldo y PTU, y los finiquitos no descuentan vacaciones. También al cerrar un periodo ordinario, la plataforma abre la corrida del periodo siguiente para que puedas empezar a trabajarla, siempre que ese periodo ya esté generado y no esté cerrado, que ningún periodo posterior del mismo calendario tenga ya una corrida y que quien cierra tenga también permiso para calcular nómina. Por eso, cerrar un periodo anterior no abre uno intermedio. Si no se puede abrir, el cierre se completa igual: en la pestaña **Bitácora** de la corrida cerrada aparece **Siguiente periodo sin abrir** y abres el periodo siguiente de forma manual. Las nóminas extraordinarias y los finiquitos no abren ninguna corrida. ![Corrida cerrada](/images/guides/getting-started/run-closed.webp) Puedes pagar la corrida desde la misma pantalla, antes o después de cerrarla: la guía [Dispersión](/docs/payroll/dispersion) explica cómo generar el lote, descargar el archivo bancario y confirmar el envío. Si ya enviaste un lote y el neto de algún empleado no coincide con lo enviado al banco, el diálogo de cierre muestra el aviso **Diferencias con lo enviado al banco**, con cuántos empleados quedan por pagar o se pagaron de más. El aviso no impide cerrar. Cerrar no timbra los recibos. Cuando confirmes que todo está pagado, tímbralos desde la misma corrida: la guía [CFDI de nómina](/docs/cfdi#timbrar-una-nomina) explica cómo. ## Cambios después del cierre [#cambios-después-del-cierre] Puede pasar que, ya cerrado el periodo, captures una incidencia con fecha dentro de él, corrijas una incidencia o un concepto de esas fechas, autorices horas extra de esas fechas, des de alta a un empleado con fecha de ingreso en ese periodo o registres una baja con fecha dentro de él. Lo ya pagado no se modifica. La plataforma calcula la diferencia y la registra como un **ajuste retroactivo** que se aplica en la siguiente nómina. ### Confirmar cambio retroactivo [#confirmar-cambio-retroactivo] Antes de guardar un cambio así, la plataforma te pide confirmarlo en el diálogo **Confirmar cambio retroactivo**. El diálogo indica qué fecha cae en un periodo ya cerrado y cuál es ese periodo, por ejemplo **Periodo 18/2026**. ![Diálogo Confirmar cambio retroactivo con el periodo 18/2026](/images/guides/payroll/retro-confirm-dialog.webp) Haz clic en **Confirmar ajuste** para guardar el cambio y generar el ajuste, o en **Cancelar** para no guardar nada. ## La tarjeta Ajustes retroactivos [#la-tarjeta-ajustes-retroactivos] Los ajustes aparecen en la tarjeta **Ajustes retroactivos**, arriba de las pestañas de la corrida. Cada ajuste muestra el empleado, el periodo de origen, qué lo generó (**Cambio de incidencias**, **Cambio de conceptos** o **Cambio de datos del empleado**), una descripción con las fechas, quién lo generó y cuándo, y el importe neto. Pasa el cursor sobre el importe para ver el detalle por concepto. **En una corrida cerrada**, la tarjeta lista las correcciones que generaron los cambios posteriores al cierre de ese periodo. Se aplican en el siguiente periodo abierto. ![Tarjeta Ajustes retroactivos en la corrida cerrada del periodo 18](/images/guides/payroll/retro-card-closed-run.webp) **En una corrida abierta**, la tarjeta lista las correcciones de periodos cerrados que ese cálculo absorbe. Al calcular o recalcular, el recibo del empleado incluye la diferencia como línea de ajuste retroactivo, y en la tabla de empleados su fila lleva la etiqueta **Ajuste retroactivo**. ![Tarjeta Ajustes retroactivos en la corrida del periodo 19 con un ajuste aplicado](/images/guides/payroll/retro-card-open-run.webp) Si una corrección negativa no se pudo descontar completa porque el neto llegó a cero, lo que falta pasa a la siguiente nómina como **Arrastre de periodo anterior**. ## Cancelar un ajuste [#cancelar-un-ajuste] Mientras la corrida que lo absorbe siga abierta, puedes cancelar un ajuste. 1. En la tarjeta **Ajustes retroactivos** de la corrida abierta, haz clic en **Cancelar** junto al ajuste. 2. Revisa el diálogo **¿Cancelar el ajuste retroactivo?**, que indica el empleado, el periodo y el importe. 3. Haz clic en **Cancelar ajuste**. Para dejarlo como está, haz clic en **Conservar**. Un ajuste cancelado no se aplica en ningún periodo. Si ya estaba aplicado en la corrida, recalcúlala antes de cerrar. Un ajuste que ya se pagó en un periodo cerrado no se puede cancelar. # Dispersión (/docs/payroll/dispersion) Una vez calculada, la corrida muestra la pestaña **Dispersión**, y la conserva después de cerrar el periodo. Desde ahí generas el lote de pagos de una cuenta emisora, descargas el archivo que subes al portal del banco y confirmas cuando ya lo enviaste. La pestaña aparece solo si tu rol permite ver las corridas de nómina y los sueldos. Para generar y confirmar un lote necesitas además permiso para cerrar la nómina, y para generar, ver la configuración de nómina, donde están las cuentas emisoras. ## Generar el lote [#generar-el-lote] 1. En la corrida calculada o cerrada, abre la pestaña **Dispersión** y haz clic en **Lote**. 2. En **Cuenta emisora**, elige la cuenta desde la que pagas. Solo aparecen las cuentas activas de la razón social de la corrida. 3. Revisa la **Fecha valor**, el día en que el banco aplica los pagos. Por omisión es la fecha de pago del periodo. 4. Haz clic en **Generar**. El primer lote de una corrida toma el neto de cada recibo. Cada lote muestra cuántos empleados quedaron **Cubiertos**, cuántos **Retenidos** y el **Total a dispersar**, que solo suma a los cubiertos. En **Empleados cubiertos** ves la tabla de pagos con la CLABE y el banco de cada empleado. Un lote solo incluye lo que falta por pagar. Un empleado que ya recibió su neto en un lote enviado, o que está en el lote sin enviar de otra cuenta, no se repite: nadie se paga dos veces. Cada lote lleva su número, por ejemplo **Lote 2**, y los que pagan una diferencia después de un lote enviado llevan la etiqueta **Diferencia**. Cada cuenta emisora puede tener varios lotes **Enviado** en la misma corrida y, a la vez, un solo lote **Sin enviar**. ## Corregir a los retenidos [#corregir-a-los-retenidos] En **Empleados retenidos** aparece cada empleado que no puede pagarse por transferencia, con la razón: **Cobra en efectivo**, **Sin neto a pagar**, **Sin CLABE registrada**, **CLABE inválida** o **El banco de la CLABE no existe en el catálogo del SAT**. Su nombre lleva a su expediente para que corrijas el dato, o a su recibo cuando no tiene neto a pagar. Después de corregirlo, haz clic en **Lote** otra vez y genera con la misma cuenta: mientras el lote esté **Sin enviar**, generarlo de nuevo lo actualiza. ## Pagar una diferencia después de recalcular [#pagar-una-diferencia-después-de-recalcular] Puedes recalcular una corrida que todavía no cierras aunque ya hayas enviado un lote, por ejemplo para agregar un bono. Cuando la corrida tiene un lote enviado, la pestaña muestra la tabla **Neto, enviado y por pagar** con el **Neto** de cada empleado, lo **Enviado** al banco y lo que queda **Por pagar**. Para pagar lo que falta, haz clic en **Lote** y genera otro lote: incluye solo a los empleados con un monto por pagar, por esa diferencia. Si ya no queda ningún pago por incluir, la plataforma lo indica y no genera el lote; si la cuenta tenía un lote **Sin enviar**, lo elimina. Si el neto de un empleado baja de lo que ya se envió, la tabla muestra el excedente en la columna **Pagado de más**. La plataforma no descuenta ese excedente: recupéralo capturando una deducción en el siguiente periodo. Un lote **Sin enviar** que ya no coincide con lo que queda por pagar lo indica y oculta **Confirmar envío**; haz clic en **Regenerar** para actualizarlo con la misma cuenta. Si al regenerar ya no queda nada por pagar, la plataforma elimina ese lote sin enviar. ## Registrar un rechazo del banco [#registrar-un-rechazo-del-banco] Si el banco rechaza un pago de un lote **Enviado**, regístralo para que el empleado entre en el siguiente lote: 1. En **Empleados cubiertos** del lote enviado, abre el menú del pago y haz clic en **Registrar rechazo**. 2. Escribe el **Motivo del rechazo** y haz clic en **Registrar rechazo**. El pago queda marcado como **Rechazado** y su monto vuelve a quedar por pagar. Un rechazo registrado no se puede deshacer, así que confirma el dato con el banco antes de registrarlo. Necesitas permiso para cerrar la nómina. ## Descargar el archivo bancario [#descargar-el-archivo-bancario] 1. En el lote, haz clic en **Archivo**. 2. Si el banco pide datos de tu contrato que la cuenta emisora no tiene guardados, o los guardados ya no cumplen el formato del banco, captúralos y haz clic en **Continuar**. Para no capturarlos en cada descarga, corrígelos en la cuenta emisora: en la columna **Parámetros de contrato** aparecen como **Pendientes**. 3. Haz clic en **Descargar** junto a cada archivo. Algunos bancos separan los pagos en más de un archivo, por ejemplo los del mismo banco y los interbancarios. El diálogo también lista los pagos que el archivo rechaza o que el banco emisor no puede pagar, para que los corrijas y vuelvas a generar el lote. Para revisar el lote antes de subirlo, haz clic en **Descargar Excel** en el mismo diálogo: trae los pagos del lote y los empleados retenidos con su motivo. El Excel es solo para revisar; al banco se sube el archivo. Si el banco emisor no tiene un formato de archivo en la plataforma, el diálogo lo indica: paga ese lote por otro medio y después confírmalo. ## Confirmar el envío [#confirmar-el-envío] Sube el archivo al portal del banco y, cuando lo hayas enviado, haz clic en **Confirmar envío** y luego en **Confirmar envío** en el diálogo **Confirmar envío al banco**. El lote pasa a **Enviado** y ya no se modifica. Para pagar algo más desde la misma cuenta, genera otro lote. **Confirmar envío** está deshabilitado mientras falte descargar algún archivo del lote o quede algún pago fuera de los archivos. Pasa el cursor sobre el botón para ver qué falta. Si generas el lote de nuevo, descarga otra vez sus archivos antes de confirmar. El estado de pago de la última corrida cerrada también aparece en la tarjeta **Nómina del periodo** del [Dashboard](/docs/dashboard). # Exportaciones y permisos (/docs/payroll/exports-and-permissions) Una corrida calculada o cerrada se puede exportar a Excel, completa o sin importes individuales, y cada recibo se puede descargar por separado. Los permisos de nómina definen quién podrá ver, calcular, cerrar o exportar. ## Exportar la corrida [#exportar-la-corrida] Con la corrida en **Calculado** o **Cerrado**, abre **Opciones** en la corrida. Ahí encontrarás dos exportaciones: ![Menú Opciones con Exportar a Excel y Exportar nómina ciega](/images/guides/payroll/run-options-menu.webp) **Exportar a Excel** descarga el libro de trabajo para revisar la nómina, con cuatro hojas: * **Resumen**: los totales de la corrida por concepto, con su clave y su nombre. * **Detalle**: una fila por empleado y una columna por concepto, encabezada con su clave y su nombre (por ejemplo, **P006 - Bono**). Las cuotas de IMSS e INFONAVIT van en columnas fijas, de **4001 Riesgo de trabajo - PAT** a **I004 INFONAVIT patronal**: **PAT** es la parte del patrón y **EMP** la del empleado. Al final están **Costo social patronal**, **Costo social empleado**, que suma lo que se le descontó de IMSS, y **Costo total empresa**. * **Conceptos**: una fila por cada concepto de cada empleado, lista para tablas dinámicas. * **Informativos**: las provisiones de cada empleado, para contabilidad de costos. En **Resumen**, **Detalle** e **Informativos** cada concepto aparece con su clave y su nombre, como **P001 - Sueldo**. En **Conceptos** la clave va en su propia columna, **Código**. Lo que no es un concepto del catálogo, como el IMSS patronal por rama en **Resumen** o el impuesto estatal, aparece solo con su nombre, y en **Código** lleva su identificador de cálculo, como `isn`. **Exportar nómina ciega** descarga un libro pensado para que la dirección autorice la nómina sin ver lo que gana cada persona: * **Autorización**: los totales generales (empleados, percepciones, deducciones y neto a depositar), las percepciones y deducciones por concepto, con su clave y su nombre, y el costo patronal. * **Personal**: una fila por empleado con su número, nombre, puesto, días pagados, faltas, incapacidades e incidencias, sin importes. ## Descargas por empleado [#descargas-por-empleado] Desde el menú de la fila de un empleado en la corrida, o desde **Opciones** en su recibo: * **Descargar recibo (PDF)**: el recibo del empleado. Es informativo y no sustituye el CFDI de nómina timbrado. * **Descargar Excel del empleado**: el detalle completo de su recibo y sus incidencias del periodo. La guía [Recibos y cálculo paso a paso](/docs/payroll/receipts#descargar-el-recibo) describe su contenido. Para reportes de varias corridas, de un mes, de un año o de un empleado, usa [Reportes](/docs/payroll/reports). ## Permisos de nómina [#permisos-de-nómina] En el editor de roles de **Configuración → Usuarios y permisos → Permisos**, la nómina se controla con estos módulos: | Módulo y permiso | Qué controla | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Nómina → Corridas de nómina: Ver** | Entrar a **Nómina → Cálculo**, ver los calendarios, las corridas y sus empleados. Junto con **Sueldos: Ver**, entrar a **Nómina → Reportes** y a **Nómina → SBC bimestral**. | | **Nómina → Corridas de nómina: Ejecutar** | Abrir periodos, capturar incidencias y conceptos desde la corrida, calcular y recalcular, aplicar cambios de empleados, ver y cancelar ajustes retroactivos, y en **Nómina → SBC bimestral** recalcular el bimestre y ajustar, aprobar o rechazar propuestas. | | **Nómina → Corridas de nómina: Cerrar periodo** | Cerrar periodos calculados. El cierre es definitivo. | | **Capital Humano → Sueldos: Ver** | Ver sueldos, SBC e importes; abrir recibos, exportar la corrida, descargar recibos y descargar reportes. | | **Asistencia e incidencias → Asistencia: Ver** | Entrar a **Nómina → Reportes** y descargar el **Registro de jornada para inspección**, con los empleados que ese permiso te deja ver. No da acceso a los reportes de nómina. | **Próximamente.** Salvo en **Nómina → Reportes** y **Nómina → SBC bimestral**, que ya aplican los permisos de la tabla, estos permisos todavía no restringen la nómina: por ahora, cualquier usuario de la organización puede ver y hacer todo en ella, sin importar su rol. Los roles que configures se aplicarán cuando se active el control de permisos. Consulta [Usuarios y permisos](/docs/configuration/access). # Operar la nómina (/docs/payroll) Cada periodo de un calendario de nómina se opera con el mismo ciclo. Esta guía resume el ciclo y te lleva a la página de cada paso. Si todavía no configuras tu organización, empieza por [Primeros pasos](/docs/getting-started). ## El ciclo de un periodo [#el-ciclo-de-un-periodo] 1. **Abrir el periodo.** En **Nómina → Cálculo**, entra al calendario y abre el periodo. Se crea la corrida del periodo con los empleados activos de ese calendario. 2. **Capturar incidencias.** Registra faltas, retardos, horas extra, incapacidades y otros movimientos del periodo. 3. **Calcular.** Con **Calcular nómina** se generan los recibos de todos los empleados de la corrida. 4. **Revisar los recibos.** Revisa los totales de la corrida y el recibo de cada empleado. Si corriges algo, recalcula las veces que haga falta. 5. **Cerrar.** Cuando todo esté correcto, cierra el periodo. El cierre es definitivo. ## Estatus de una corrida [#estatus-de-una-corrida] La corrida muestra su estatus junto al número, por ejemplo **Corrida #2 · Calculado**. | Estatus | Qué significa | | -------------- | ------------------------------------------------------------------------------------------ | | **Borrador** | El periodo está abierto y la corrida aún no se calcula. | | **Calculando** | El cálculo está en proceso. | | **Calculado** | Los recibos están listos para revisarse. Todavía puedes capturar incidencias y recalcular. | | **Cerrado** | El periodo está cerrado y ya no se puede recalcular ni reabrir. | | **Cancelado** | La corrida se canceló; el periodo se puede volver a abrir. | Debajo del encabezado, una barra de tres pasos te dice en qué punto del ciclo estás: **Abierto** (captura incidencias y calcula la nómina), **Calculado** (revisa recibos y recalcula si hay cambios) y **Cerrado** (fechas bloqueadas; los cambios generan ajustes retroactivos). ![Corrida calculada con la barra de pasos Abierto, Calculado y Cerrado](/images/guides/payroll/run-17-calculated.webp) ## En esta sección [#en-esta-sección] * [Abrir y calcular un periodo](/docs/payroll/run): abrir el periodo, capturar incidencias y conceptos, calcular, recalcular y leer los totales de la corrida. * [Recibos y cálculo paso a paso](/docs/payroll/receipts): el recibo de cada empleado y cómo se llegó a cada importe. * [Cierre y ajustes retroactivos](/docs/payroll/closing): cerrar el periodo y qué pasa con los cambios posteriores. * [Dispersión](/docs/payroll/dispersion): pagar la corrida calculada o cerrada con el archivo del banco y confirmar el envío. * [Reportes](/docs/payroll/reports): los reportes estándar en Excel de una corrida, de un mes, de un año o de un empleado, los archivos SUA del mes y el registro de jornada para una inspección. * [Exportaciones y permisos](/docs/payroll/exports-and-permissions): los archivos que puedes descargar y quién puede hacer cada cosa. * [Reparto de PTU](/docs/payroll/ptu): repartir la PTU del ejercicio entre los trabajadores de una razón social. ## Otros tipos de nómina [#otros-tipos-de-nómina] Las corridas de este ciclo son de nómina ordinaria. La plataforma también maneja corridas de **Finiquito**, de **Aguinaldo** y de **PTU**. La corrida de finiquito se crea al confirmar un finiquito en **Nómina → Finiquitos**; la guía [Finiquito y liquidación](/docs/terminations#escenarios) explica cómo. La de aguinaldo se crea en **Nómina → Aguinaldo** y la de PTU en **Nómina → PTU**; la guía [Reparto de PTU](/docs/payroll/ptu) explica cómo. # Reparto de PTU (/docs/payroll/ptu) La corrida de PTU reparte entre los trabajadores de una razón social el monto que determinó la declaración anual del ejercicio. La encuentras en **Nómina → PTU**. Necesitas permiso para calcular la nómina, y para ver los montos de cada trabajador, permiso para ver sueldos. ## Cómo se reparte [#cómo-se-reparte] **Dos mitades.** La mitad del monto se reparte en proporción a los días trabajados en el año y la otra mitad en proporción al salario devengado. Los días y el salario salen de los recibos cerrados del ejercicio: cuentan los días pagados como sueldo, incluidas las vacaciones, y las incapacidades por riesgo de trabajo y por maternidad. El salario devengado suma los conceptos que integran la base de PTU, por omisión el sueldo, las vacaciones y las comisiones. **Quién participa.** Participan todos los trabajadores subordinados que trabajaron para la razón social en el año, incluidos los que se dieron de baja. Los asimilados y los de honorarios no participan. Un trabajador eventual (contrato por obra determinada, por tiempo determinado o de temporada) participa solo si trabajó al menos 60 días en el año. Además, puedes excluir a un trabajador al corregir los datos de la corrida. **El tope.** Ningún trabajador recibe más que la cantidad mayor entre tres meses de su salario y el promedio de la PTU que recibió en los últimos tres años. Lo que pase del tope se reparte entre los demás. Si todos llegan a su tope, el resto queda sin repartir. **Impuestos.** La PTU se paga con el concepto **PTU pagada**. Está exenta hasta 15 veces la UMA en el año y el excedente paga ISR con el método configurado en ese concepto. El recibo de PTU no lleva sueldo ni subsidio para el empleo. ## Crear la corrida [#crear-la-corrida] 1. En **Nómina → PTU**, haz clic en **Nueva corrida**. 2. Elige la **Razón social**. 3. En **Año fiscal**, escribe el ejercicio cuyas utilidades repartes. 4. En **Monto a repartir**, captura la PTU por repartir de la declaración anual. 5. Elige la **Fecha de pago**. Debe ser posterior al año fiscal. 6. Haz clic en **Crear corrida**. La corrida se crea calculada y abierta. En ella revisas el reparto antes de cerrarla y, si hace falta, corriges los datos de un trabajador: el recibo de cada trabajador trae su PTU, la parte exenta, el ISR y el neto. La suma de lo repartido coincide con el monto, centavo por centavo, salvo lo que quede sin repartir por topes. Los asimilados y los de honorarios no entran a la corrida. Solo puede haber una corrida de PTU por razón social y año fiscal. Si ya existe, la plataforma te lo indica con el número de la corrida. ## Corregir los datos de un trabajador [#corregir-los-datos-de-un-trabajador] Antes de cerrar la corrida puedes corregir los datos con que se reparte: los días trabajados, el salario devengado, el promedio de PTU de los últimos tres años y si un trabajador participa. Sirve para excluir a quien no deba participar, para completar meses que no se pagaron en la plataforma o para capturar la PTU que un trabajador recibió en años anteriores. 1. En la corrida, haz clic en **Capturar PTU**. El botón aparece mientras la corrida está abierta o calculada, si tienes permiso para calcular la nómina y para ver sueldos. 2. Corrige las celdas de **Días trabajados**, **Salario devengado**, **Promedio PTU últimos 3 años** o **Excluir**. Las columnas **Tope**, **PTU** y **Participa** muestran el reparto actual y no se editan. Los días, el salario y el promedio necesitan un número: si escribes otra cosa, se conserva el valor anterior. Si dejas vacía la celda de días o de salario también se conserva; si vacías el promedio, se elimina. 3. Haz clic en **Validar** y revisa el panel de errores. 4. Haz clic en **Publicar** y confirma. Al publicar, el monto se reparte de nuevo entre toda la plantilla y la corrida se recalcula: el cambio de un trabajador mueve la PTU de los demás, y la suma sigue coincidiendo con el monto. Un trabajador excluido recibe cero y su parte se reparte entre los demás. Si subes el promedio de un trabajador, su tope sube con él. Los cambios quedan guardados mientras no publiques, así que puedes cerrar la pantalla y volver con **Capturar PTU**. **Descartar sesión** elimina los cambios sin publicar. Mientras alguien publica los datos de la corrida, **Capturar PTU** te pide intentar de nuevo en unos minutos. ## Cerrar y pagar [#cerrar-y-pagar] Revisa los recibos y cierra la corrida como una de nómina ordinaria. Consulta [Cierre y ajustes retroactivos](/docs/payroll/closing) y [Dispersión](/docs/payroll/dispersion). # Recibos y cálculo paso a paso (/docs/payroll/receipts) Cada empleado de una corrida calculada tiene su recibo. Ábrelo desde la tabla de empleados de la corrida con el ojo de su fila o con **Ver detalle** en el menú de la fila. ## El encabezado del recibo [#el-encabezado-del-recibo] Arriba del recibo están el nombre del empleado, su número y la razón social, y debajo su **RFC**, **CURP** y **NSS**, el **Periodo**, las **Fechas** y la **Fecha de pago**. El botón **Opciones** ofrece las mismas acciones que el menú de la fila en la corrida: capturar una incidencia o un concepto (mientras el periodo esté abierto), descargar el recibo y copiar el número de empleado. El recibo tiene cuatro pestañas: **Resumen**, **Incidencias**, **Conceptos** y **Cálculo paso a paso**. En una corrida extraordinaria no aparecen **Incidencias** ni **Conceptos**. ## Resumen [#resumen] La pestaña **Resumen** empieza con el **Pago del periodo**: percepciones menos deducciones igual al **Neto a depositar**. ![Pestaña Resumen del recibo con el neto a depositar](/images/guides/getting-started/receipt-summary.webp) Debajo hay dos tablas: * **Percepciones**: cada concepto pagado, como el sueldo, con su parte exenta cuando la tiene. Si el subsidio para el empleo es mayor que el ISR, lo que sobra se paga, sin pasar del subsidio del periodo, y aparece como la percepción **Subsidio para el empleo**. En algunas nóminas cerradas, lo que sobra aparece como **Subsidio para el empleo (informativo)** y no se pagó. * **Deducciones**: cada concepto descontado, como el ISR, el IMSS del trabajador, las faltas o los créditos. Al final, **Lo que le cuesta a la empresa** muestra la carga patronal del empleado y el **Costo total empresa**. Esa carga no afecta el neto del trabajador. Si el cálculo dejó avisos para el empleado, aparecen al inicio de la pestaña bajo **Avisos del cálculo**. ### Ver desglose [#ver-desglose] Junto al nombre de muchos conceptos hay un ícono de información. Pasa el cursor sobre él para ver cómo se calculó ese importe: * **Fórmula**: la regla que se aplicó, por ejemplo sueldo diario por días del periodo. * **Sustitución**: la misma fórmula con los valores del empleado y el resultado. * Los datos que usó el cálculo y, a veces, una nota que explica un caso particular. ![Desglose del sueldo con la fórmula y la sustitución](/images/guides/payroll/receipt-line-breakdown.webp) ## Incidencias [#incidencias] La pestaña **Incidencias** muestra las incidencias del empleado que tomó el cálculo, con el origen y quién capturó cada una. En un periodo cerrado muestra el registro tal como quedó al cierre. Mientras el periodo esté abierto puedes capturar desde aquí una incidencia para este empleado. Si no tiene incidencias, verás **Sin incidencias para este periodo**. ![Pestaña Incidencias del recibo sin incidencias](/images/guides/payroll/receipt-incidences.webp) ## Conceptos [#conceptos] La pestaña **Conceptos** muestra los bonos, comisiones, descuentos y demás conceptos capturados para el empleado que toma el cálculo. En un periodo cerrado muestra los que pagó la corrida, y no se pueden editar. Mientras el periodo esté abierto puedes editar o eliminar un monto, o usar **Capturar concepto** para registrar uno nuevo para este empleado. La fecha se limita a los días del periodo. Si no tiene conceptos, verás **Sin conceptos para este periodo**. ## Cálculo paso a paso [#cálculo-paso-a-paso] La pestaña **Cálculo paso a paso** es la cédula del recibo: recorre el cálculo completo, movimiento por movimiento, en seis secciones. ![Pestaña Cálculo paso a paso con las bases del cálculo](/images/guides/getting-started/receipt-walkthrough.webp) 1. **Bases del cálculo.** Los valores de los que parte el periodo: sueldo diario, factor de integración, salario base de cotización (SBC) diario y topado, y días cotizados al IMSS. 2. **Determinación del sueldo del periodo.** El sueldo completo del periodo menos los días que no se pagan. El resultado es el sueldo que entra a la base del ISR. 3. **Percepciones.** Cada concepto percibido con su parte gravada y exenta de ISR. La columna gravada forma la base del impuesto. 4. **Determinación del ISR.** El cálculo con la tarifa: cuota fija del rango más la tasa sobre el excedente del límite inferior. Si el empleado recibe subsidio para el empleo, la sección muestra el ISR causado, el subsidio aplicado y el ISR retenido; la línea **ISR** del recibo es el ISR retenido. Si sobra subsidio, la sección cierra con el importe que se le paga al empleado, que nunca pasa del subsidio del periodo. 5. **Cuotas IMSS del trabajador.** Cada ramo a cargo del trabajador, calculado sobre el SBC topado por los días que cotiza ese ramo. 6. **Determinación del neto a depositar.** Del total de percepciones se restan las percepciones no monetarias y cada deducción; una columna muestra el saldo después de cada movimiento. **En el periodo con ajuste anual de ISR, la sección 4 incluye la conciliación del año.** Después de **ISR retenido del periodo** aparece **ISR a cargo del ajuste anual** o **ISR a favor del ajuste anual**, y luego el **ISR retenido** final. Debajo está el detalle del año: **Ingreso gravable del año**, la tarifa anual, **ISR del año con la tarifa anual**, **Subsidio para el empleo del año**, **ISR anual a cargo (nunca menor a cero)**, **ISR retenido en el año, este periodo incluido** y, al final, **ISR a cargo del trabajador** o **ISR a favor del trabajador**. Un saldo a cargo aumenta la deducción de ISR. Un saldo a favor primero reduce el ISR del periodo; si es mayor que ese ISR, la diferencia se le devuelve al empleado en las percepciones como **Reintegro de ISR retenido en exceso**. Si el empleado quedó fuera del ajuste, la sección muestra el motivo. Consulta [Ajuste anual de ISR](/docs/payroll/run#ajuste-anual-de-isr). Para un trabajador asimilado a salarios o por honorarios, las secciones lo indican: no tiene salario base de cotización ni recibe subsidio para el empleo. Por qué cada importe es el que es, paso por paso, se explica en [Cómo se calcula la nómina](/docs/calculation#mapa-del-recibo). ## Descargar el recibo [#descargar-el-recibo] Desde **Opciones** en el recibo, o desde el menú de la fila en la corrida, puedes descargar: * **Descargar recibo (PDF)**: el recibo del empleado en PDF, con la clave de cada percepción y deducción junto a su nombre, como P001 - Sueldo. Es un documento informativo y no sustituye el CFDI de nómina timbrado ante el SAT. * **Descargar Excel del empleado**: un libro con el detalle completo del recibo (datos del empleado, días y bases, cada concepto y el costo patronal) y una hoja con las incidencias del periodo, marcadas por color según su origen. Para exportar la corrida completa, consulta [Exportaciones y permisos](/docs/payroll/exports-and-permissions). # Reportes (/docs/payroll/reports) En **Nómina → Reportes** encuentras los reportes estándar de la nómina, agrupados en **Proceso de nómina**, **Mensuales**, **Por empleado** y **Analítica**, y el **Registro de jornada para inspección** en **Asistencia**. Los reportes de nómina se descargan como un archivo de Excel con un formato fijo: tú eliges los filtros y la plataforma arma el archivo con las cifras guardadas en las corridas, las mismas que ves en cada corrida y en **Exportar a Excel**. **Totales por concepto** y **Listado de nómina por empleado** también se descargan en PDF. **Archivos SUA** es la excepción: se descarga como un ZIP; consulta [Archivos SUA](#archivos-sua). Los reportes de movimientos IMSS y **Créditos INFONAVIT** tampoco salen de las corridas: leen los movimientos IMSS y los créditos de los empleados. El registro de jornada se descarga como un ZIP; consulta [Registro de jornada para inspección](#registro-de-jornada). ![Grupo Analítica del catálogo de reportes con sus cuatro reportes](/images/guides/payroll/reports-catalog.webp) Cada reporte pide sus propios permisos, y en el catálogo solo ves los que puedes descargar: * Los reportes de nómina piden los permisos para ver las corridas de nómina y los sueldos. Solo aparecen las corridas de las razones sociales a las que tienes acceso. * **Archivos SUA** pide además los permisos para ver los movimientos IMSS, las incidencias y los créditos de los empleados, y solo incluye a los empleados que esos tres permisos te dejan ver. * **Movimientos IMSS por periodo** e **Histórico de movimientos IMSS** piden además el permiso para ver los movimientos IMSS, y solo incluyen a los empleados de las razones sociales que ese permiso y el de las corridas te dejan ver. * **Créditos INFONAVIT** y **Conceptos con saldo** piden además el permiso para ver los créditos de los empleados, y solo incluyen a los empleados de las razones sociales que ese permiso y el de las corridas te dejan ver. * El **Registro de jornada para inspección** pide el permiso para ver la asistencia, y solo incluye a los empleados que ese permiso te deja ver. ## Descargar un reporte [#descargar-un-reporte] 1. En **Nómina → Reportes**, haz clic en el reporte. 2. Elige los filtros. Según el reporte, son la **Razón social** y la **Corrida**, el **Año** y el **Mes**, el **Empleado** con las fechas **Desde** y **Hasta**, o los **Empleados** con las fechas **Desde** y **Hasta**. 3. Haz clic en **Descargar**. En **Totales por concepto** y **Listado de nómina por empleado**, haz clic en **Descargar PDF** para obtenerlo en PDF. ![Diálogo del reporte Resumen de cuotas con razón social, año y mes](/images/guides/payroll/reports-dialog.webp) Solo se ofrecen las razones sociales, años y meses que tienen corridas para ese reporte, del año en curso y los cuatro anteriores. En **Reporte ajuste anual** solo aparecen los años con una corrida cerrada que aplicó el ajuste anual de ISR. Si no hay ninguna, el diálogo muestra **No se encontraron corridas para este reporte** y no permite descargar. **Archivos SUA**, **Movimientos IMSS por periodo**, **Histórico de movimientos IMSS** y **Créditos INFONAVIT** no dependen de las corridas: ofrecen el mes en curso y los 24 anteriores de las razones sociales a las que tienes acceso, y el histórico ofrece los años de esos meses; del año más antiguo solo incluye los meses dentro de ese rango. En **Conceptos con saldo** solo aparecen las corridas cerradas. En **Historial de pagos por empleado**, el rango entre **Desde** y **Hasta** puede ser de hasta tres años. En **Registro de jornada para inspección**, de hasta un año. ## Todas las razones sociales en un archivo [#todas-las-razones-sociales-en-un-archivo] Si tienes acceso a dos o más razones sociales, los reportes **Mensuales** ofrecen **Todas las razones sociales** como primera opción de **Razón social**. Con esa opción: * **Año** y **Mes** muestran los periodos de todas tus razones sociales. * El archivo suma las corridas cerradas de todas las razones sociales a las que tienes acceso. * La línea bajo el título de cada hoja muestra el nombre de la organización. Si solo tienes acceso a algunas razones sociales, muestra sus nombres. En el Excel de revisión de **Archivos SUA** muestra el mes. * Las hojas de empleados y de corridas empiezan con la columna **Razón social**. * **Archivos SUA** no lee corridas: el ZIP trae una carpeta por cada registro patronal de tus razones sociales. * **Movimientos IMSS por periodo**, **Histórico de movimientos IMSS** y **Créditos INFONAVIT** incluyen los movimientos y los créditos de todas las razones sociales a las que tienes acceso. Los reportes de **Proceso de nómina** y el **Historial de pagos por empleado** siguen siendo de una razón social o de un empleado. ## Qué corridas toma cada reporte de nómina [#qué-corridas-toma-cada-reporte-de-nómina] * Los reportes de **una corrida** aceptan corridas **Calculado** y **Cerrado**, para que revises la nómina antes de cerrarla. La lista incluye corridas ordinarias y de aguinaldo. **Conceptos con saldo** solo acepta corridas **Cerrado**, porque lo retenido de cada crédito se registra al cerrar la corrida. * Los reportes **mensuales**, **anuales** y **por empleado** suman solo corridas **Cerrado**, así sus cifras ya no cambian. Incluyen finiquitos. * **Archivos SUA** no lee corridas: toma los movimientos IMSS, las incidencias y los créditos INFONAVIT del mes. * **Movimientos IMSS por periodo** e **Histórico de movimientos IMSS** no leen corridas: toman los movimientos IMSS por su fecha de movimiento, en cualquier estatus. * **Créditos INFONAVIT** lista los créditos vigentes en el mes y suma lo retenido en las corridas **Cerrado** de ese mes. También incluye un crédito ya liquidado o terminado cuando una corrida cerrada del mes le retuvo. * **Comparativo de costo mensual** compara el mes que eliges con el mes anterior (enero, con diciembre del año anterior). Si el mes anterior no tiene corridas cerradas, lo compara contra cero. * **Comparativo de costo por corrida** acepta corridas **Calculado** y **Cerrado** y las compara con la corrida cerrada anterior: la del periodo previo del mismo calendario o, en aguinaldo y PTU, la anterior del mismo tipo y razón social. Si no hay ninguna, el periodo anterior aparece como **Ninguna corrida cerrada anterior** con costo cero. * Una corrida cuenta en el mes de acumulación de su periodo. Una corrida de aguinaldo cuenta en el mes de su fecha de pago, y un finiquito en el mes de la baja. Descargar el mismo reporte con los mismos filtros da las mismas cifras. Solo cambia la línea **Generado el** del encabezado. **Archivos SUA** toma los movimientos IMSS, las incidencias y los créditos tal como están al descargar, así que refleja cualquier captura posterior; un movimiento ya presentado en el IDSE conserva el NSS y el registro patronal con que se presentó. Los reportes de movimientos IMSS, **Créditos INFONAVIT** y **Conceptos con saldo** también leen los movimientos y los créditos tal como están al descargar: lo retenido se conserva, y el estatus, el valor del descuento y el monto original reflejan cualquier edición posterior. ## Los reportes [#los-reportes] ### Proceso de nómina [#proceso-de-nómina] | Reporte | Filtros | Qué contiene | | ---------------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Totales por concepto** | Corrida | Percepciones (gravado, exento e importe), deducciones y costo patronal sumados por concepto, con cuántos empleados tienen cada uno, la **Cantidad** (días u horas) de los conceptos que la tienen, las **Incidencias** (cuántas faltas, retardos o incapacidades hay detrás de cada deducción por días; una incapacidad de varios días con un mismo folio cuenta una vez), y un resumen de la corrida. En PDF, la primera página muestra percepciones y deducciones con su cantidad, sus incidencias e importe, sus totales, el neto y el total de departamentos y empleados; la segunda, el detalle con empleados, gravado y exento en las percepciones, e incidencias en las deducciones y el costo patronal. | | **Listado de nómina por empleado** | Corrida y orden | Una fila por empleado con todos sus conceptos y totales, su departamento y su centro de costos, y con las mismas columnas de cuotas de IMSS e INFONAVIT que la hoja **Detalle** del Excel de la corrida. Ordenado por **Departamento**, agrega la hoja **Por departamento** con el total de cada uno. En PDF, el encabezado muestra el tipo de corrida (por ejemplo **Ordinaria** o **Aguinaldo**) y las fechas del periodo, y cada empleado es un bloque con sus percepciones, deducciones y neto, sin las líneas en cero; ordenado por **Departamento**, los bloques se agrupan por departamento con su total, y el total de la corrida va al final. | | **Netos a dispersar** | Corrida | El neto de cada empleado con su banco, cuenta bancaria y CLABE, y la hoja **Por banco**. | | **Costos por periodo** | Corrida | Percepciones, deducciones, neto, costo patronal y costo total, en las hojas **Por departamento** y **Por centro de costos**. | | **Conceptos con saldo** | Corrida | Los créditos y préstamos con monto original de los empleados de una corrida cerrada. Un crédito sin monto original, como INFONAVIT o una pensión alimenticia, no aparece. Cada fila lleva el monto original, lo retenido en la corrida, lo retenido acumulado hasta el periodo de la corrida y el saldo, y la hoja **Totales** por concepto. | ### Mensuales [#mensuales] | Reporte | Filtros | Qué contiene | | ----------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Acumulado mensual por empleado por concepto** | Razón social, año y mes | Lo pagado a cada empleado en el mes por concepto, en las hojas **Percepciones**, **Deducciones** y **Costo patronal**. | | **Impuesto estatal sobre nómina** | Razón social, año y mes | El impuesto estatal de cada empleado con su estado y el total de sus percepciones, y la hoja **Por estado**. Con **Todas las razones sociales**, **Por estado** separa cada estado por razón social. | | **Resumen de cuotas** | Razón social, año y mes | IMSS patronal y obrero, INFONAVIT e impuesto estatal por empleado y por registro patronal. Separa lo que se paga el día 17 de cada mes de lo que se paga cada bimestre (retiro, cesantía y vejez, e INFONAVIT). La hoja **Por rama** desglosa el IMSS por rama. Con **Todas las razones sociales**, **Por registro patronal** muestra cada registro con su razón social, y **Por rama** suma todas las razones sociales. | | **Resumen de ISR retenido y subsidio** | Razón social y año | Base gravable, ISR causado, subsidio causado, aplicado y entregado, e ISR retenido, por corrida y por empleado. | | **Reporte ajuste anual** | Razón social y año | El ajuste anual de ISR de cada empleado, de las corridas cerradas que lo aplicaron: ingreso gravable del año, tarifa anual, subsidio del año, ISR anual, ISR retenido antes y después del ajuste, e ISR a cargo o a favor del trabajador. Cada fila lleva el departamento, centro de costos, sucursal y registro patronal, y la hoja abre con los filtros de Excel activados. Los empleados que quedaron fuera aparecen como **Excluido** con su motivo. La hoja **Totales** cuenta a los ajustados y a los excluidos por motivo y suma cada importe. | | **Acumulado anual por concepto por empleado** | Razón social y año | Lo pagado a cada empleado en el año por concepto, con una columna por mes y el total. | | **Acumulado anual gravado y exento** | Razón social y año | Las percepciones del año de cada empleado por concepto, separadas en gravado y exento. | | **Archivos SUA** | Razón social, año y mes | Un ZIP con los archivos de asegurados, movimientos y créditos INFONAVIT del mes para importar en el SUA, por registro patronal, y un Excel de revisión. Consulta [Archivos SUA](#archivos-sua). | | **Movimientos IMSS por periodo** | Razón social, año y mes | Las altas, bajas y modificaciones de salario del mes por empleado, con su estatus, fecha de presentación y folio de acuse, y la hoja **Totales** por registro patronal, movimiento y estatus. | | **Histórico de movimientos IMSS** | Razón social y año | Los mismos movimientos de todo el año con la columna **Mes**, y la hoja **Totales** por mes, registro patronal, movimiento y estatus. | | **Créditos INFONAVIT** | Razón social, año y mes | Los créditos INFONAVIT vigentes en el mes por empleado, con su tipo y valor de descuento y lo retenido en las corridas cerradas del mes, y la hoja **Totales** por registro patronal y tipo de descuento. | ### Por empleado [#por-empleado] | Reporte | Filtros | Qué contiene | | ----------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Historial de pagos por empleado** | Empleado, desde y hasta | Cada corrida cerrada que se le pagó en el rango, con una columna por concepto encabezada con su clave y su nombre, y la hoja **Por concepto** con el total de cada uno. | ### Analítica [#analítica] | Reporte | Filtros | Qué contiene | | ------------------------------------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Costo de nómina en el tiempo** | Razón social y año | La hoja **Resumen mensual**, con empleados, percepciones, deducciones, neto, IMSS, INFONAVIT, impuesto estatal, otro costo patronal y costo total empresa de cada mes. Una hoja por departamento, centro de costos, puesto, sucursal, registro patronal, estado, tipo de trabajador, calendario y tipo de corrida, con el costo total empresa de cada mes y del año. | | **Comparativo de costo mensual** | Razón social, año y mes | Cuánto cambió el costo total empresa contra el mes anterior y por qué. La hoja **Puente** separa el cambio en plantilla (altas y bajas), periodos pagados, movimientos de salario, incidencias, cambios normativos, pagos extraordinarios y otros, con los puntos porcentuales de cada uno. **Por empleado** muestra esa misma separación de cada persona, y **Cambios normativos** lista los valores que cambiaron, como la UMA o el salario mínimo, con su cifra anterior y la nueva. | | **Comparativo de costo por corrida** | Corrida | Las mismas hojas del comparativo mensual, para una corrida contra la corrida cerrada anterior. | | **Costo real por empleado** | Razón social, año y mes | Lo que cuesta cada empleado en el mes: sueldos y percepciones en efectivo, IMSS, INFONAVIT, impuesto estatal, prestaciones y otro costo patronal, costo total empresa, provisiones (lo que se acumula para pagar después, como aguinaldo, vacaciones y prima vacacional) y costo total con provisiones. La hoja **Por departamento** suma lo mismo por departamento, y en **Por empleado** cada persona muestra todos los departamentos en que se le pagó en el mes. | Cada reporte de analítica termina con la hoja **Detalle por recibo**: una fila por recibo con su corrida, periodo, empleado, departamento, puesto y demás datos de la corrida, y sus importes. Todas las cifras de las demás hojas salen de esas filas, así que al filtrarlas llegas a los recibos detrás de cualquier cifra. En los comparativos la hoja trae los recibos de los dos periodos, con la columna **Periodo comparado**, y por eso no lleva fila **Totales**. ### Asistencia [#asistencia] | Reporte | Filtros | Qué contiene | | --------------------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Registro de jornada para inspección** | Empleados, desde y hasta | Un ZIP con el Excel de la jornada, las checadas, los cambios manuales y los acuses de cada empleado, y la carpeta **evidencias** con los archivos de los cambios. | ## Registro de jornada para inspección [#registro-de-jornada] Ante una inspección laboral tienes que entregar el registro de jornada de cada trabajador y probar de qué día y de dónde es cada checada, con los justificantes de los cambios. Este reporte lo junta en un solo archivo. 1. En **Nómina → Reportes**, haz clic en **Registro de jornada para inspección**. 2. En **Empleados**, elige **Todos los empleados** o **Elegir empleados**. Para elegir, busca a cada empleado por nombre o número y selecciónalo; cada uno aparece como una etiqueta, y la **x** de la etiqueta lo saca de la lista. Puedes elegir hasta 100 empleados uno por uno; para más, utiliza **Todos los empleados**. 3. Elige **Desde** y **Hasta**, con un rango de hasta un año. 4. Haz clic en **Descargar**. El archivo se arma en ese momento; con muchos empleados puede tardar unos segundos. Si alguno de los empleados que elegiste no está en tu alcance o no estuvo de alta en ningún día del rango, el archivo se descarga sin él y la plataforma te avisa cuántos quedaron fuera. Si las evidencias de los cambios manuales de esos empleados en el rango suman más de 200 MB, la plataforma no arma el archivo y te pide reducir el rango de fechas o los empleados. El ZIP trae el Excel y la carpeta **evidencias**. El Excel tiene cuatro hojas: * **Jornada:** una fila por empleado y por cada día del rango en que estuvo de alta, con el horario, el estatus del día, la entrada, las salidas y regresos, la salida, las horas trabajadas, los retardos, las faltas, las horas extra autorizadas y las pendientes de autorizar, el tiempo libre (vacaciones, permiso, incapacidad, descanso o festivo) y las marcas del dispositivo, como **Dispositivo distinto**. Cada día se lee igual que en el panel de asistencia. * **Checadas:** una fila por checada, con su hora en la zona horaria en que se registró, el origen (**Escaneo QR**, **Captura manual**, **Captura masiva**, **Reloj checador** o **Corrección aprobada**), el código QR, el alcance de ubicación, la latitud y longitud, el teléfono con que se checó y sus marcas. * **Cambios:** cada checada capturada o eliminada a mano, con quién lo hizo y cuándo, el motivo y el nombre del archivo de evidencia dentro de la carpeta **evidencias**. Una checada eliminada sigue aquí aunque ya no aparezca en **Checadas**. * **Acuses:** por ahora solo trae los encabezados, porque los empleados todavía no dan el acuse de su registro. Si un archivo de evidencia no se puede leer del almacenamiento, el ZIP se descarga de todos modos y su fila dice **Evidencia no disponible**. Lo mismo pasa con la evidencia de un cambio que también incluye checadas de empleados que no puedes ver. Cada descarga queda registrada al hacer clic en **Descargar**, con quién la hizo, cuándo, el rango y los empleados que incluyó. El archivo preparado solo se puede bajar durante los 10 minutos siguientes. ## Archivos SUA [#archivos-sua] **Archivos SUA** arma los archivos que importas en el SUA para determinar las cuotas obrero-patronales del mes. El ZIP trae una carpeta por registro patronal, con su número sin guiones, y dentro de ella los archivos que tienen líneas: * **asegurados.txt:** los trabajadores dados de alta por primera vez en ese registro patronal durante el mes, con su tipo de trabajador IMSS, su semana o jornada reducida y su crédito INFONAVIT si lo tienen. * **movimientos.txt:** las bajas, las modificaciones de salario y los reingresos con fecha dentro del mes, aunque ya se hayan presentado en el IDSE; las faltas y los permisos sin goce de sueldo, una línea por cada grupo de días seguidos; y las incapacidades, una línea por folio en cada mes en que el certificado tiene días, con su fecha de inicio y su total de días. * **creditos-infonavit.txt:** los créditos INFONAVIT que inician en el mes, salvo el que ya va en la línea de alta del trabajador en **asegurados.txt**. Junto a las carpetas va **revision-sua-AAAA-MM.xlsx**. La hoja **Archivos** dice cuántas líneas tiene cada archivo, y la hoja **Pendientes** lista a los trabajadores que no se incluyeron y el motivo: sin NSS, sin registro patronal, sin tipo de trabajador IMSS o semana o jornada reducida, un alta o movimiento sin salario base de cotización, una incapacidad sin folio, un crédito sin número o un dato que no cabe en el archivo. Cada trabajador aparece una vez por archivo y motivo. Corrige esos datos y vuelve a descargar. Un mes sin movimientos descarga solo el Excel de revisión. ## Cómo están armados los archivos [#cómo-están-armados-los-archivos] * **Listas:** cada hoja de lista tiene una fila por registro, sin renglones de subtotal en medio, para que puedas usar BUSCARV, filtros y tablas dinámicas. Los totales por grupo van en su propia hoja, y una fila **Totales** cierra cada hoja. * **Código:** la columna muestra la clave del concepto en el recibo, como P001, D001 u O001. En los reportes con una columna por concepto, como el **Listado de nómina por empleado** y el **Historial de pagos por empleado**, el encabezado lleva la clave y el nombre, como P001 - Sueldo. Las cuotas de IMSS e INFONAVIT del **Listado de nómina por empleado** llevan claves propias del reporte, de 4001 a 4013, I004 e I005, que no son conceptos del catálogo. * **Departamento y otros datos del empleado:** departamento, centro de costos, sucursal y registro patronal son los que el empleado tenía en esa corrida. Si alguien cambió después de departamento, los reportes de corridas anteriores lo siguen mostrando en el anterior. * **Por empleado:** si una persona se dio de baja y se recontrató en la misma razón social, sus pagos se suman en una sola fila. Si trabaja en dos razones sociales, en **Todas las razones sociales** tiene una fila en cada una. En el **Reporte ajuste anual** cada empleado tiene una fila por razón social, con su ajuste más reciente en ella. * **IMSS por rama:** el **Resumen de cuotas** calcula el IMSS de cada rama por empleado y lo redondea a centavos, como el SUA. Por eso la suma de las ramas puede diferir por algunos centavos del total de IMSS de la corrida. * **Impuesto estatal:** el reporte muestra el impuesto calculado, no la base gravable. * **Comparativos:** los siete motivos del **Puente** suman exactamente el cambio entre los dos periodos. * **Pagos extraordinarios:** lo pagado en corridas de aguinaldo, PTU y finiquito va siempre aquí, también la PTU de alguien que ya se dio de baja. * **Plantilla:** cuenta a quien se dio de alta desde que empezó el periodo anterior o de baja antes de que termine el actual. Si el periodo anterior no tiene corridas ordinarias cerradas, todos cuentan como altas. Alguien más que solo aparece en uno de los dos periodos se compara contra cero, como si continuara. * **Periodos pagados:** un periodo de más o de menos, por ejemplo un mes con una quincena cerrada contra uno con dos, o un mes de cinco semanas contra uno de cuatro. * **Movimientos de salario:** el cambio de su salario diario, solo por los días que se pagaron con el salario nuevo. * **Incidencias:** las faltas, incapacidades y permisos sin goce, y las percepciones de horas extra, días festivos y de descanso trabajados y prima dominical. * **Cambios normativos:** el cambio en IMSS, INFONAVIT e impuesto estatal que no explica su sueldo, solo si cambió algún valor oficial entre los dos periodos en el mismo estado. Un cambio de estado del empleado no cuenta como cambio normativo. El detalle nombra las corridas que no guardaron sus valores oficiales. * **Otros:** lo que queda. * **Ajuste anual:** las cifras del ajuste son las mismas que muestra el recibo en **Cálculo paso a paso**. **ISR retenido con ajuste** es el ISR retenido en todas las corridas cerradas del año de esa razón social, el mismo que el **Resumen de ISR retenido y subsidio**. Los recibos, la exportación de la corrida y la nómina ciega siguen en la corrida; consulta [Exportaciones y permisos](/docs/payroll/exports-and-permissions). El archivo para el banco está en [Dispersión](/docs/payroll/dispersion). # Abrir y calcular un periodo (/docs/payroll/run) Esta página cubre la primera mitad del ciclo: desde abrir el periodo hasta tener la corrida calculada y lista para revisar. ## Encontrar el calendario [#encontrar-el-calendario] Entra a **Nómina → Cálculo**. La pantalla **Cálculo de nómina** lista tus calendarios con su razón social, periodicidad, año, número de empleados, el periodo actual con su estatus y cuántos periodos llevas cerrados o en curso. ![Lista de calendarios en Cálculo de nómina](/images/guides/payroll/calculation-calendars.webp) Haz clic en **Ver calendario** en el calendario que vas a operar. ## Abrir el periodo [#abrir-el-periodo] La página del calendario muestra arriba el **Periodo actual** con sus fechas y su fecha de pago, y abajo la tabla con todos los periodos del año, su número de empleados, el neto a depositar y su estatus. 1. En el recuadro **Periodo actual**, haz clic en **Abrir periodo**. También puedes abrir otro periodo desde su fila en la tabla. 2. La plataforma crea la corrida del periodo y te lleva a ella. Si el periodo ya tiene corrida, en lugar de **Abrir periodo** verás **Ver corrida**. ![Calendario con el periodo actual y la tabla de periodos](/images/guides/getting-started/calendar-open-period.webp) Al abrir el periodo, la corrida toma una copia de los datos de los empleados activos de ese calendario: sueldo, contrato y demás datos de nómina. Si después cambian, la corrida te avisa y tú decides cuándo aplicar los cambios (ver [Cambios en los datos de los empleados](#cambios-en-los-datos-de-los-empleados)). Si la razón social del calendario tiene la identidad fiscal incompleta, la página muestra el aviso **Identidad fiscal incompleta** con los datos que faltan. El aviso no impide abrir ni calcular la nómina; esos datos se necesitarán para timbrar los recibos. Consulta [CFDI de nómina](/docs/cfdi). ### Si el periodo no se abre [#si-el-periodo-no-se-abre] La plataforma muestra un mensaje con el motivo: * **Sin empleados activos para este calendario.** Ningún empleado activo tiene asignado este calendario. Revisa el calendario de nómina en la sección **Alta a nómina** de cada empleado. * **2 de 5 empleados no están listos para nómina**, con las cifras de tu calendario. Algún empleado activo tiene incompletos sus datos de nómina. Revisa que todos estén dados de alta como **En nómina**; la guía [Primeros pasos](/docs/getting-started#empleado-listo-para-nomina) explica qué datos se revisan. * **El calendario está inactivo.** Actívalo en **Configuración → Nómina → Calendarios**. En un calendario inactivo no aparece el botón **Abrir periodo**. * **Error al abrir el periodo.** Intenta de nuevo. Si se repite, revisa que las prestaciones del contrato de los empleados no estén por debajo del mínimo legal: la plataforma no abre el periodo en ese caso. ## La página de la corrida [#la-página-de-la-corrida] El encabezado muestra el número de corrida, su estatus, la razón social, el calendario, las fechas del periodo, la fecha de pago y el número de empleados. Debajo está la barra de pasos **Abierto**, **Calculado** y **Cerrado**. ![Corrida recién abierta en estatus Borrador](/images/guides/payroll/run-draft.webp) La corrida tiene hasta cinco pestañas: * **Empleados**: los totales de la corrida y la tabla de empleados. Antes de calcular solo muestra un aviso de que la corrida aún no está calculada. * **Incidencias**: las incidencias del periodo de todos los empleados de la corrida. * **Conceptos**: los bonos, comisiones, descuentos y demás conceptos capturados para los empleados de la corrida en el periodo. * **Dispersión**: el pago por transferencia de la corrida calculada o cerrada, para quien puede ver los sueldos (ver [Dispersión](/docs/payroll/dispersion)). * **Bitácora**: el historial de la corrida (periodo abierto, nómina calculada, recalculada, cambios aplicados, periodo cerrado) con su fecha. ## Capturar incidencias [#capturar-incidencias] Al calcular, la plataforma aplica automáticamente las incidencias de asistencia registradas en el periodo, incluidas las que llegan del portal de colaborador y del reloj checador. Las que falten, captúralas desde la corrida con el botón **Incidencias**: * El botón **Incidencias** abre la captura masiva: una hoja con los empleados de la corrida y los días del periodo. * La flecha junto al botón despliega **Captura individual…**, que abre el diálogo **Capturar asistencia** para uno o varios empleados, **Captura masiva** y, separada, **Capturar conceptos** (ver [Capturar conceptos](#capturar-conceptos)). En la captura individual, las fechas se limitan a las del periodo. En la captura masiva, llena la hoja, haz clic en **Validar**, corrige lo que marque el panel de errores y haz clic en **Publicar**. La guía [Registro de asistencia y conceptos](/docs/attendance/incidences) explica ambas capturas con detalle. También puedes capturar la incidencia de un solo empleado desde su fila en la tabla de empleados, con **Capturar incidencia** en el menú de la fila. La pestaña **Incidencias** lista lo que la nómina toma del periodo con su fecha, empleado, tipo, cantidad, quién la capturó y su origen. Las horas extra pendientes de autorizar o rechazadas no aparecen ahí porque no se pagan; las ves en el registro de asistencia. Arriba verás cuántas se capturaron desde Nómina y cuántas llegaron del portal de colaborador. Desde ahí también puedes buscar, filtrar y utilizar **Capturar incidencia**. ![Pestaña Incidencias con dos retardos y una falta injustificada](/images/guides/payroll/run-incidences-tab.webp) **En una corrida calculada, cada captura recalcula sola.** Si la corrida ya está calculada, al guardar o publicar una incidencia con fecha dentro del periodo, los recibos de los empleados afectados se recalculan automáticamente. ## Capturar conceptos [#capturar-conceptos] Los bonos, comisiones, descuentos y demás conceptos con monto capturado también se registran desde la corrida. Abre **Incidencias → Capturar conceptos** en el encabezado, o haz clic en **Capturar conceptos** en la pestaña **Conceptos**. 1. En el diálogo **Capturar conceptos**, marca en la lista **Conceptos** los que vas a capturar. La lista empieza vacía y solo muestra los conceptos que algún empleado de la corrida puede recibir. 2. En la lista **Empleados**, deja marcados a quienes aplica. Empiezan marcados todos los empleados de la corrida. Cada lista tiene su buscador y una casilla para seleccionar todos. 3. Elige la **Fecha de aplicación**. Solo puedes elegir días del periodo; de entrada es el último día. 4. Haz clic en **Continuar**. Se abre la hoja **Captura de conceptos**, con una fila por empleado y concepto, y una sola columna de monto: la fecha que elegiste. Llena los montos, haz clic en **Validar** y luego en **Publicar**. Cada empleado solo tiene filas de los conceptos que puede recibir. Si la corrida ya tiene una captura de conceptos sin publicar, el diálogo **Captura de conceptos sin publicar** te deja **Reanudar** esa hoja o **Descartar** y empezar una nueva con lo que seleccionaste. Los montos publicados se registran en los conceptos de la organización, igual que si los capturaras desde **Conceptos**. Si la corrida ya está calculada, se recalcula al publicar. Si la fecha cae en un periodo ya cerrado, la plataforma te pide confirmar y genera un ajuste retroactivo. Para un solo empleado, usa **Capturar concepto** en el menú de su fila en la tabla de empleados. Se abre el diálogo **Capturar conceptos** para ese empleado, con la fecha limitada al periodo; de entrada es hoy, o el día del periodo más cercano. La pestaña **Conceptos** lista los montos que toma la corrida: los del periodo de sus empleados mientras está abierta, y los que pagó una vez cerrada. Desde ahí puedes buscar, filtrar por concepto y fecha, editar o eliminar un monto, y usar **Capturar conceptos**. En una corrida cerrada la lista es de solo lectura. Los créditos (INFONAVIT, FONACOT, pensión alimenticia y préstamos) no se capturan como conceptos: salen de los créditos del empleado. La excepción es **Crédito INFONAVIT pendiente**, que se captura aquí como un descuento único. Los descuentos por días tampoco se capturan: salen de sus incidencias. ## Calcular la nómina [#calcular-la-nómina] Haz clic en **Calcular nómina**. Al terminar, la plataforma confirma cuántos empleados se calcularon y la corrida pasa a **Calculado**. Si aparece **Falta configurar parámetros fiscales para esta periodicidad**, faltan tablas fiscales para la periodicidad del calendario; revísalo con quien administra los catálogos de la organización antes de volver a calcular. ### Horas extra por autorizar [#horas-extra-por-autorizar] La nómina solo paga las horas extra autorizadas. Las que siguen pendientes de autorizar o se rechazaron quedan fuera de los recibos. La guía [Incidencias y conceptos](/docs/attendance/incidences#autorizacion-horas-extra) explica quién las autoriza. Si quedan horas extra del periodo sin autorizar, la corrida muestra, desde que abres el periodo y también antes de calcular, la nota **Horas extra por autorizar** con cuántos registros y cuántas horas faltan. **Ver solicitudes** te lleva a [Solicitudes](/docs/requests#horas-extra) para decidirlas. Cuando se autorizan con la corrida abierta, los recibos se recalculan solos. La nota no impide cerrar el periodo. Si cierras con horas extra pendientes, no se pagan en esa nómina; si se autorizan después del cierre, entran como ajuste retroactivo en la siguiente. ### Días incompletos [#dias-incompletos] Un día laborable al que le faltan checadas se paga como día trabajado mientras nadie capture la checada que falta o una falta. Si el periodo tiene días así que ya terminaron, la corrida muestra la nota **Días incompletos** con cuántos empleados y cuántos días quedan. **Ver checadas** te lleva a [Checadas](/docs/attendance/time-clock) con el periodo, la razón social y el calendario de la corrida, y el filtro **Checadas incompletas** aplicados. La nota no impide calcular ni cerrar. Si cierras con días incompletos y después se captura una falta de esos días, entra como ajuste retroactivo en la siguiente nómina. Con **Derivar faltas** encendida, los días sin ninguna checada se vuelven falta solos; consulta [Retardos y faltas](/docs/attendance/lateness#derivar-faltas). ## Recalcular [#recalcular] En una corrida calculada, el botón principal cambia a **Recalcular**. Úsalo después de corregir datos o si quieres aplicar cambios pendientes. Se abre el diálogo **¿Recalcular la nómina?**, que te dice qué se va a aplicar: * **Cambios de empleados**: cuántos datos modificados, empleados nuevos, reemplazos o bajas se incorporarán. * **Asistencia**: si hay incidencias nuevas o modificadas que el cálculo aún no toma. Además, cada recálculo toma los conceptos como están configurados en ese momento: si cambiaste la fórmula, las casillas o las bases de un concepto, o lo desactivaste, el cambio se aplica aunque el diálogo no lo liste. Si hay cambios, el botón de confirmación dice **Sincronizar y recalcular**; si no, **Recalcular**. Los importes y el neto a depositar pueden cambiar. ![Diálogo Recalcular la nómina](/images/guides/payroll/recalculate-dialog.webp) Si hay una captura masiva sin publicar para el periodo, el diálogo te lo advierte: lo capturado en ese borrador no se aplica en el recálculo. Publícalo o descártalo. ### Sincronización pendiente [#sincronización-pendiente] Normalmente la corrida se mantiene al día sola. Si una actualización automática no se completó, la corrida muestra la nota **Sincronización pendiente** con las incidencias nuevas, modificadas o eliminadas que faltan por aplicar. Haz clic en **Sincronizar** para aplicarlas. Mientras la nota esté visible no podrás cerrar el periodo. ## Cambios en los datos de los empleados [#cambios-en-los-datos-de-los-empleados] Si después de abrir el periodo cambia algo de un empleado que afecta su pago, como su sueldo o su contrato, o si un empleado se vuelve elegible o causa baja, la corrida calculada muestra un aviso amarillo: **Hay cambios en la información de empleados que aún no se aplican a esta corrida**. ![Aviso de cambios de empleados sobre la pestaña Bitácora de la corrida](/images/guides/payroll/run-log-tab.webp) 1. Haz clic en **Revisar y aplicar cambios**. El diálogo **Cambios de empleados** muestra las diferencias entre los datos actuales y la copia de la corrida, agrupadas en **Modificados**, **Nuevos**, **Reemplazos** y **Dados de baja**. 2. Selecciona los cambios que quieres incorporar y haz clic en **Aplicar cambios**. 3. Confirma en **Aplicar cambios de empleados**. La corrida se recalcula y la operación queda en la bitácora. Los cambios que no apliques se quedan pendientes. **Recalcular** los aplica todos. ## Ajuste anual de ISR [#ajuste-anual-de-isr] En el último periodo ordinario del año, el que termina en diciembre (o en enero del año siguiente, en un calendario semanal o catorcenal), puedes aplicar el ajuste anual de ISR. La plataforma calcula el ISR del año completo de cada empleado con la tarifa anual, le resta el subsidio para el empleo del año y lo compara con todo lo que se le retuvo en el año, este periodo incluido. La diferencia se suma al ISR del periodo si retuviste de menos, o se le devuelve al empleado si retuviste de más. 1. En la corrida, abre **Opciones** y elige **Ajuste anual de ISR**. La opción solo aparece en el último periodo ordinario del año, mientras la corrida esté abierta. 2. En el diálogo **Ajuste anual de ISR**, marca **Aplicar el ajuste anual de ISR en este periodo**. 3. En **Empleados excluidos del ajuste**, elige solo a quienes te pidieron por escrito presentar su propia declaración anual. Si nadie lo pidió, déjalo vacío. 4. Haz clic en **Guardar**. Si la corrida ya está calculada, se recalcula al guardar; si el recálculo falla, no se guarda nada. Si todavía no la calculas, el ajuste se aplica en el primer cálculo. El cambio queda en la **Bitácora**. **El ajuste llega a los empleados marcados.** Solo se ajusta a los empleados de sueldos y salarios que tienen marcada la casilla **Aplica ajuste anual de ISR**, en la sección **Contratación** del empleado o en la columna **Ajuste anual ISR** de la edición masiva. Todos los empleados de sueldos y salarios vienen marcados; desmarca a quien no debe ajustarse. **Las exclusiones se aplican solas.** No necesitas elegir en **Empleados excluidos del ajuste** a estos empleados; la plataforma los deja fuera del ajuste por su cuenta: * Quien no laboró todo el año con la razón social: entró después del 1 de enero o causó baja antes del 1 de diciembre. * Quien tuvo ingresos en el año mayores a $400,000.00, sumando lo gravado y lo exento. * Los empleados de sueldos y salarios sin la casilla marcada. * Los asimilados y los trabajadores por honorarios: el ajuste anual solo aplica a sueldos y salarios. **El ajuste se ve en la tabla y en el recibo.** En la tabla de empleados, cada empleado ajustado lleva la etiqueta **Ajuste anual** y cada empleado que quedó fuera, **Sin ajuste anual**; pasa el cursor sobre esta última para ver el motivo. En el recibo, la pestaña **Cálculo paso a paso** muestra la conciliación del año (ver [Recibos y cálculo paso a paso](/docs/payroll/receipts)). Con la corrida cerrada, el **Reporte ajuste anual** de [Reportes](/docs/payroll/reports) reúne el ajuste de todos los empleados en un archivo de Excel. Si al calcular aparece **No se encontró la tarifa anual de ISR del ejercicio**, falta la tarifa anual en [Catálogos de plataforma](/docs/configuration/platform-catalogs). ## Los totales de la corrida [#los-totales-de-la-corrida] Con la corrida calculada, la pestaña **Empleados** muestra dos paneles: * **Lo que reciben los trabajadores**: percepciones totales, deducciones con su detalle (faltas, ISR, IMSS del trabajador y demás) y el **Neto a depositar**. * **Lo que le cuesta a la empresa**: percepciones en efectivo más el costo patronal (IMSS patronal, INFONAVIT patronal e impuesto sobre nómina) y el **Costo total empresa**. ![Totales de una corrida con faltas y séptimo día descontados](/images/guides/payroll/run-17-calculated.webp) Debajo está **Costos y provisiones**: las provisiones de aguinaldo, vacaciones, prima vacacional, ISN y cuotas patronales, y el **Costo total del empleado**. **Este panel es informativo.** No aparece en los recibos ni afecta el neto; sirve para conocer el costo real de la plantilla. ## La tabla de empleados [#la-tabla-de-empleados] Al final de la pestaña **Empleados** está la tabla con una fila por empleado: días pagados, faltas, incapacidades, incidencias, sueldo diario, SBC, percepciones, subsidio, ISR, IMSS, deducciones, neto a depositar y el costo patronal y de la empresa. Usa **Buscar empleado…** para encontrar a alguien. ![Tabla de empleados de la corrida](/images/guides/payroll/run-employees-table.webp) Algunas filas llevan una etiqueta: * **Ajuste retroactivo**: el recibo incluye una corrección de un periodo cerrado. Consulta [Cierre y ajustes retroactivos](/docs/payroll/closing). * **Ajuste anual**: el recibo incluye el ajuste anual de ISR. Consulta [Ajuste anual de ISR](#ajuste-anual-de-isr). * **Sin ajuste anual**: el empleado quedó fuera del ajuste anual de ISR. Pasa el cursor sobre la etiqueta para ver el motivo. * **Aviso**: el cálculo dejó avisos para ese empleado; consúltalos en su recibo. * **Revisar neto**: las retenciones de ley superan las percepciones del periodo. Revisa el sueldo diario y las incidencias del empleado antes de cerrar. El ojo de cada fila abre el recibo del empleado. El menú de la fila ofrece además **Capturar incidencia**, **Capturar concepto**, las descargas del recibo y **Copiar número de empleado**. El recibo se explica en [Recibos y cálculo paso a paso](/docs/payroll/receipts). # Finiquito y liquidación (/docs/terminations) Cuando termina una relación laboral, el empleado tiene derecho a cobrar lo que se le debe hasta su último día. Ese pago es el **finiquito**, y se debe siempre, sin importar por qué termina la relación. La **liquidación** es lo que se paga además del finiquito cuando el motivo de la baja lo exige, como en un despido injustificado. ## Lo que incluye el finiquito [#finiquito] * **Salario pendiente**: los días trabajados que ninguna nómina ha pagado todavía, hasta el último día laborado. * **Aguinaldo proporcional**: la parte del aguinaldo del año que corresponde a los días trabajados, sin las faltas injustificadas, los permisos y las vacaciones sin goce ni las incapacidades por enfermedad general si su contrato tiene **Considerar ausentismo en el aguinaldo**. Si el aguinaldo de ese año ya se pagó, no se vuelve a pagar. * **Vacaciones proporcionales**: los días de vacaciones que acumuló en su año de servicio, menos los que ya registró si eliges descontarlos en el escenario, con su **prima vacacional**, salvo que su contrato tenga **No se calcula** en **Pago de prima vacacional**. * **Vacaciones de periodos anteriores**: los días de años de servicio anteriores que no tomó. Los captura RH en el escenario y no llevan prima vacacional. El aguinaldo y las vacaciones corresponden a los trabajadores asalariados. Los asimilados y los trabajadores por honorarios no los acumulan. ## Lo que agrega la liquidación [#liquidacion] * **Indemnización de 90 días** de salario diario integrado. * **20 días por año de servicio**, también sobre el salario diario integrado. El último año incompleto se paga en proporción. * **Prima de antigüedad**: 12 días por año de servicio. Su salario base no puede ser menor a un salario mínimo ni mayor a dos salarios mínimos, sin importar cuánto gane el empleado. La indemnización y la prima de antigüedad comparten una sola exención de ISR de 90 UMA por año de servicio. Lo que rebasa esa exención paga impuesto. ## Qué corresponde según el motivo [#segun-el-motivo] | Motivo de la baja | Indemnización (90 días y 20 días por año) | Prima de antigüedad | | ----------------------------- | ----------------------------------------- | -------------------------------------------- | | Despido injustificado | Sí | Sí | | Despido justificado | No | Sí | | Fallecimiento | No | Sí | | Renuncia voluntaria | No | Solo con 15 años de servicio cumplidos o más | | Terminación por mutuo acuerdo | No | No | | Terminación del contrato | No | No | En todos los casos se paga el finiquito. En una renuncia, los 15 años deben estar cumplidos: una fracción del año quince no cuenta. ## Cómo lo calcula la plataforma [#calculo] La plataforma calcula el finiquito y la liquidación con las reglas de esta página: toma el motivo de la baja para decidir qué conceptos corresponden, y usa el sueldo, el salario diario integrado y la antigüedad del empleado a su último día laborado. Los parámetros fiscales, como la UMA y las tablas de ISR, también se toman a esa fecha desde los [Catálogos de plataforma](/docs/configuration/platform-catalogs). Si no hay valores para el último día laborado, el cálculo no se hace y aparece el aviso **Sin parámetros fiscales para la fecha de baja, por ejemplo la UMA o las tablas de ISR de ese año. Revisa la fecha o los Catálogos de plataforma.** ## Calcular un finiquito [#escenarios] Los finiquitos se calculan en **Nómina → Finiquitos**. Cada cálculo es un **escenario**: calcularlo no da de baja al empleado ni paga nada, y puedes crear varios para el mismo empleado y compararlos. 1. Haz clic en **Nuevo finiquito** y elige al empleado. 2. Escribe una **Referencia** para distinguir el escenario, por ejemplo 1 o 2. 3. Elige la **Fecha de baja** y el **Motivo de baja**. 4. En **Antigüedad para vacaciones**, elige si las vacaciones proporcionales se calculan con los días de su **Año en curso** (el valor inicial) o con los del **Último año cumplido**. 5. En **Vacaciones registradas en el periodo**, elige **Descontar** para restar de las vacaciones los días que el empleado ya registró desde su último aniversario, o **No descontar** (el valor inicial). 6. Haz clic en **Calcular escenario**. Al terminar se abre la página del escenario. Para calcular a varios empleados con la misma fecha y el mismo motivo, abre la flecha junto a **Nuevo finiquito** y elige **Nuevo finiquito masivo**. La tabla separa los escenarios en las vistas **Borradores**, **Definitivos**, **Pagados** y **Cancelados**, y se filtra por **Razón social**, **Fecha de baja** y **Finiquito masivo**. ## La página del escenario [#pagina-del-escenario] Al abrir un escenario, las acciones están arriba, antes de los datos del empleado. Solo aparecen las que aplican al estatus del escenario: * **Descargar**: la carta del escenario en PDF. No aparece en un escenario cancelado. Si el escenario tiene una sola carta, se descarga directo y al pasar el cursor ves cuál es; si tiene dos, eliges cuál en un menú. * **Recalcular**, en un borrador: vuelve a calcular con los datos actuales del empleado. * **Confirmar**, en un borrador: lo marca como definitivo. Se crea y se cierra la corrida de finiquito con esos montos, el empleado queda dado de baja y se cancelan sus otros borradores. No se puede deshacer, y el diálogo te muestra antes lo que va a cambiar. * **Registrar pago**, en un definitivo: captura la **Fecha de pago**, que no puede ser anterior a la fecha de baja ni posterior a hoy. Desde ese momento el recibo del finiquito se puede timbrar. * **Nuevo complemento**, en un pagado: paga lo que faltó en ese finiquito, con su misma fecha y motivo de baja. * **Ver corrida**, cuando el escenario ya tiene su corrida de finiquito. Debajo, el escenario tiene tres pestañas: **Resumen**, **Cálculo paso a paso** e **Historial de ajustes**. **Resumen** empieza con las **Percepciones** y **Deducciones** del finiquito, cada una con sus días u horas y su importe, y los totales **Total de percepciones**, **Total de deducciones** y **Neto a pagar**. En un borrador, si tienes el permiso para ejecutar la nómina, el menú de cada fila te deja revisar el cálculo antes de descargar la carta: * **Editar días** en el salario pendiente y en las dos líneas de vacaciones abre los **Días capturados**, donde también modificas **Antigüedad para vacaciones** y **Vacaciones registradas en el periodo**. Al guardar, el escenario se recalcula. * **Editar** en el aguinaldo, la prima vacacional y las indemnizaciones ajusta la línea por días, por monto o sin pagarla; **Restablecer** regresa al cálculo legal. El ISR y las demás deducciones se recalculan solos, y cada ajuste queda en el **Historial de ajustes**. La carta en PDF muestra los mismos importes, con los días u horas de cada percepción en la columna **Días / hrs**. Para recalcular, confirmar, registrar el pago o crear un complemento necesitas el permiso para ejecutar la nómina. ## Cartas de finiquito, liquidación y renuncia [#cartas] Cada escenario ofrece las cartas que le corresponden, en PDF. Un escenario cancelado no ofrece ninguna: * **Carta de liquidación**, si el escenario paga indemnización de 90 días o de 20 días por año. La prima de antigüedad sola no la vuelve liquidación. * **Carta finiquito**, en cualquier otro caso. * **Carta de renuncia**, además, cuando el motivo de la baja es **Renuncia voluntaria**. Las descargas desde: * El botón **Descargar** de la página del escenario. * El menú de opciones de la fila, o el clic derecho, en la tabla de **Finiquitos** y en la de un finiquito masivo: **Descargar carta finiquito**, **Descargar carta de liquidación** o **Descargar carta de renuncia**, una por cada carta del escenario. * La sección **Finiquitos** de la [página del empleado](/docs/employees/profile#finiquitos), con el mismo menú en cada escenario. **Los vales dispersados a tarjeta.** Los vales de despensa que se pagan en el finiquito, y los del contrato con método de entrega **Efectivo**, aparecen como una percepción más. Los que se dispersan a la tarjeta de vales no forman parte del importe neto, y la carta tampoco lista su deducción **Vales de despensa entregados**: la carta los muestra en su propio renglón, **Vales de despensa dispersados a tarjeta**, debajo del total, para que el empleado también firme de recibido. **La carta de renuncia sin escenario.** En la sección **Finiquitos** de la página del empleado, el botón **Renuncia** te pide la **Fecha de baja** que se imprime y descarga la carta con esa fecha, aunque el empleado no tenga ningún escenario. La fecha no puede ser anterior a la fecha de ingreso del empleado. El texto de cada carta sale de la plantilla de la razón social del empleado; la guía [Plantillas de documentos](/docs/configuration/document-templates) explica cómo editarlo. Para descargar las cartas necesitas los permisos para ver las corridas de nómina y para ver sueldos. # Vacaciones (/docs/time-off) La plataforma lleva el saldo de vacaciones de cada empleado: suma los días que le corresponden en cada aniversario según la tabla de vacaciones de su contrato. Las vacaciones que el empleado toma y los permisos se capturan como incidencias de asistencia. ## Saldos de vacaciones [#saldos] Entra a **Incidencias → Tiempo libre**. La pantalla muestra a los empleados activos con su **Saldo (días)**. El botón **Configuración** lleva a **Configuración → Asistencia → Tiempo libre**, donde el interruptor **Recursos Humanos** decide si las solicitudes del portal pasan también por tu revisión después del jefe directo. Aplica a las solicitudes nuevas. ## Cómo se generan los días [#cómo-se-generan-los-días] Los días se otorgan completos en cada aniversario del empleado. El aniversario se cuenta desde la **Fecha de antigüedad** si el empleado la tiene capturada; si no, desde su **Fecha de ingreso**. Un empleado con menos de un año de servicio todavía no tiene días generados. Si el empleado se da de baja, ya no se generan días después de su fecha de baja. Los días de cada aniversario salen de la tabla de **Vacaciones** del contrato del empleado, según sus años cumplidos de servicio. El contrato base utiliza la tabla de la Ley Federal del Trabajo: | Años cumplidos de servicio | Días de vacaciones | | -------------------------- | ------------------ | | 0 | 12 | | 1 | 14 | | 2 | 16 | | 3 | 18 | | 4 | 20 | | 5 a 9 | 22 | | 10 a 14 | 24 | | 15 a 19 | 26 | | 20 a 24 | 28 | | 25 o más | 30 | Si tu empresa da más días, la tabla de vacaciones del contrato los refleja. Consulta [Prestaciones](/docs/configuration/contracts#prestaciones). ## Ajustar saldos [#ajustar-saldos] Cuando empiezas a usar la plataforma, o cuando el saldo calculado no coincide con tus registros, puedes capturar el saldo real de cada empleado. 1. En **Incidencias → Tiempo libre**, marca a los empleados y haz clic en **Ajustar saldos**. 2. Elige la **Fecha de corte**: la fecha a la que corresponden los saldos reales que vas a capturar. 3. Haz clic en **Continuar**. 4. La hoja muestra el **Saldo calculado (días)** de cada empleado a esa fecha. Escribe el **Saldo real (días)** solo donde difiera, con máximo dos decimales. 5. Haz clic en **Validar** y luego en **Publicar**. ![Diálogo Ajustar saldos de vacaciones con la fecha de corte](/images/guides/time-off/adjust-balances-dialog.webp) Al publicar, la plataforma registra un ajuste por la diferencia entre el saldo calculado y el saldo real. El saldo calculado no se borra: el ajuste se suma o se resta sobre él. ## Capturar vacaciones y permisos [#capturar-vacaciones-y-permisos] Las vacaciones que el empleado toma y los permisos se capturan como incidencias con **Capturar incidencias**, en **Incidencias → Asistencia**, con el tipo que corresponda: * **Vacaciones pagadas** o **Vacaciones no pagadas**. * **Permiso con goce de sueldo** o **Permiso sin goce de sueldo**. Las vacaciones pueden ser de día completo o de medio día. En el diálogo **Capturar asistencia** eliges la **Duración por fecha**: **Día completo** o **Medio día** aplican a todas las fechas del rango, y con **Capturar un rango de fechas** también aparece **Por fecha**, que lista cada fecha del rango para que marques cuáles son de medio día. Por ejemplo, del 1 al 6 con medio día el 5 y el 6 es una sola captura **Por fecha**. En la tabla de **Capturar incidencias**, la **Cantidad** de una vacación es 1 o 0.5. Medio día de vacación ocupa la fecha completa: ese día no admite otra incidencia de día completo. En **Vacaciones pagadas** y en los permisos, un rango solo cuenta los días hábiles del horario de cada empleado, igual que una solicitud: sus días de descanso y los días festivos de ley no se registran ni se descuentan del saldo, y se pagan como sueldo. Por ejemplo, del viernes 16 al martes 20 con horario de lunes a viernes se registran 3 días: el 16, el 19 y el 20. Si no tiene horario ni rotación asignados, cuenta el horario predeterminado de la organización; sin ninguno, solo se omiten los festivos. Si todas las fechas son descansos o festivos, no se captura nada. El paso a paso está en [Incidencias y conceptos](/docs/attendance/incidences#capturar). La nómina del periodo toma esos días al calcular, medios días incluidos. El saldo baja cuando se cierra la nómina ordinaria que los paga, y solo por los días capturados directamente: los de solicitudes aprobadas ya bajaron el saldo al aprobarse. Si cambias uno de esos días en un periodo que ya estaba cerrado (lo capturas tarde, lo editas o lo eliminas), el saldo se mueve cuando se cierra la nómina que paga su ajuste retroactivo. ## Solicitudes y aprobaciones [#solicitudes-y-aprobaciones] Los colaboradores también solicitan vacaciones y permisos desde el portal de colaborador, en **Tiempo libre** o en **Solicitudes**. Una solicitud de vacaciones puede ser de **Medio día** en todas sus fechas o **Por fecha**, eligiendo qué días son de medio día: al aprobarse, cada medio día queda como medio día de vacación y baja 0.5 del saldo, y cada día completo baja 1. Su jefe directo los aprueba en **Solicitudes → Por aprobar**, como explica la guía [Lo que ve el supervisor](/docs/employee-portal/supervisor#solicitudes). Si el colaborador no tiene jefe directo, o la organización pide la revisión de Recursos Humanos, la decisión se toma en **Incidencias → Solicitudes**; consulta [Solicitudes](/docs/requests). # Introducción (/docs/developers/api-reference) ## Descripción general [#descripción-general] *** Nuestra API proporciona una interfaz RESTful para interactuar con nuestros servicios. Todos los endpoints devuelven respuestas JSON y soportan métodos HTTP estándar. ## URL Base [#url-base] Todas las solicitudes a la API deben realizarse a: `https://app.integranom.com/api/v1/` ## Características Principales [#características-principales] * **Diseño RESTful** - Estructura de URLs limpia y predecible * **Respuestas JSON** - Todas las respuestas están en formato JSON * **Autenticación**- Soporte seguro para API key * **Límites de Uso** - Políticas de uso justo con límites claros ## Primeros pasos [#primeros-pasos] 1. **Crea una API key** - Crea un API Key de miembro o de integracion 1. Crea una API Key de miembro en la aplicación, en **Configuración → API Keys** 2. Crea una integración y luego una API Key en la aplicación, en **Configuración → Integraciones** 2. **Haz tu primera solicitud** - Sigue nuestra guía de inicio rápido 3. **Explora las APIs** - Consulta nuestra referencia completa de la API # SDK (/docs/developers/sdk) ## Próximamente [#próximamente] El SDK oficial se encuentra en desarrollo. Mientras tanto, la integración se realiza directamente mediante la [Referencia de la API](/docs/developers/api-reference). # Capturar (/docs/developers/api-reference/attendance/capture) ## Descripcion general [#descripcion-general] Este endpoint captura **un tipo de incidencia** (falta, vacaciones, horas extra, retardo, etc.) para un conjunto de empleados a lo largo de un **rango de fechas**. Las incidencias se registran en el **registro global de asistencia**, del que cada corrida de nómina lee. Los periodos abiertos ya calculados **se recalculan solos** con cada captura, así que sus recibos reflejan el registro de inmediato. Si ese recálculo automático no se completa, la respuesta llega con `recalc_pending: true` y el periodo queda **desactualizado** hasta que lo recalcules. Referencias por número/código: los empleados por `employee_numbers` y el tipo por `incidence_code`. El catálogo de tipos es **global y fijo de la plataforma** — el mismo conjunto de códigos para todas las razones sociales (consulta [Listar tipos de incidencia](/docs/developers/api-reference/incidence-types/list)). Una fecha que caiga dentro de un **periodo cerrado** no se aplica en silencio: se convierte en un **ajuste retroactivo** que corrige la nómina en el siguiente periodo, y debes confirmarlo con `confirm_retro: true` (ver más abajo). Las **horas extra** (`OVERTIME_DOUBLE_HOURS` y `OVERTIME_TRIPLE_HOURS`) capturadas por la API se crean **pendientes de autorizar**: no se pagan hasta que las autorizan todos los niveles configurados en la organización (jefe directo, Recursos Humanos o los dos). Su estatus se consulta en el campo `approval_status` de [Listar asistencia](/docs/developers/api-reference/attendance/list#estatus-de-autorizacion). Requiere que la API key tenga el permiso **Capturar asistencia**. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/attendance` ## Captura una incidencia en un rango [#captura-una-incidencia-en-un-rango] *** Un tipo de incidencia, un conjunto de empleados, un rango de fechas. Para un tipo de **día** (falta, vacaciones, incapacidad) se crea una entrada por día con cantidad `1`, o `0.5` en vacaciones de medio día. **Vacaciones pagadas** (`VACATION_PAID`) y los permisos con y sin goce (`LEAVE_PAID`, `LEAVE_UNPAID`) solo crean entradas en los días hábiles del horario de cada empleado: sus días de descanso y los días festivos de ley se omiten (sin horario ni rotación asignados, cuenta el predeterminado de la organización; sin ninguno, solo los festivos), y una fecha de medio día que cae en uno de ellos se ignora. El ejemplo captura una falta el 3 y el 4 de junio para dos empleados → `created: 4`. `created` es el número de entradas registradas; `retros_touched`, cuántos ajustes retroactivos se crearon o refrescaron (0 cuando ninguna fecha cae en un periodo cerrado). El rango no puede exceder **366 días**. ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Requerido | Notas | | ------------------------- | --------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `incidence_code` | string | Sí | Código del tipo de incidencia en el catálogo global (p. ej. `LATENESS`). | | `employee_numbers` | integer\[] | Sí | 1–500 empleados. Cada uno debe tener un empleo activo o pendiente. | | `start_date` / `end_date` | string (`YYYY-MM-DD`) | Sí | Rango (inclusivo). `end_date` ≥ `start_date`. Máximo 366 días. | | `quantity` | number | Condicional | Requerida para tipos `hours` y `count`. En `VACATION_PAID` y `VACATION_UNPAID`, `0.5` registra medio día por fecha y cualquier otro valor se registra como `1` (día completo, por omisión); en los demás tipos `days` se ignora y se registra `1`. | | `notes` | string | No | Máximo 500 caracteres. | | `disability_folio` | string | No | Folio del certificado de incapacidad, máximo 30 caracteres. Solo para tipos de incapacidad y un solo empleado. Se guarda en mayúsculas. Un folio pertenece a un solo empleado y sus días deben ser consecutivos. | | `confirm_retro` | boolean | No | Autoriza que las fechas dentro de un periodo cerrado se registren como ajuste retroactivo. Ver abajo. | **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/attendance" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "incidence_code": "FALTA", "employee_numbers": [1, 2], "start_date": "2026-06-03", "end_date": "2026-06-04" }' ``` **Respuesta** ```json { "created": 4, "retros_touched": 0, "recalc_pending": false } ``` ## Fechas dentro de un periodo cerrado (ajuste retroactivo) [#fechas-dentro-de-un-periodo-cerrado-ajuste-retroactivo] *** Cerrar un periodo es definitivo. Una incidencia dentro de sus fechas no reescribe la nómina ya pagada: se registra como un **ajuste retroactivo** que corrige la diferencia en el **siguiente** periodo. Por eso, si alguna fecha cae en un periodo cerrado, la captura se rechaza con `409 RETRO_CONFIRMATION_REQUIRED` (con `retro_periods`, los periodos afectados). Reenvíala con **`confirm_retro: true`** para aceptar y registrar el ajuste — la respuesta trae `retros_touched` mayor a 0. **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/attendance" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "incidence_code": "FALTA", "employee_numbers": [1], "start_date": "2026-01-05", "end_date": "2026-01-05", "confirm_retro": true }' ``` **Respuesta** ```json { "created": 1, "retros_touched": 1, "recalc_pending": false } ``` ## Cantidad (según la unidad del tipo) [#cantidad-según-la-unidad-del-tipo] | Unidad del tipo | `quantity` | Comportamiento | | --------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------ | | `days` de vacaciones (`VACATION_PAID`, `VACATION_UNPAID`) | Opcional | Una entrada por día: `0.5` es medio día y cualquier otro valor se registra como `1`. | | Demás `days` (falta, incapacidad, permiso…) | Se ignora | Una entrada por día con cantidad `1`. | | `hours` (horas extra) | **Requerida** | Las horas capturadas por día del rango. | | `count` (retardos) | **Requerida** | El número de ocurrencias por día del rango. | ## Forma de la respuesta [#forma-de-la-respuesta] `200` — `{ "created": number, "retros_touched": number, "recalc_pending": boolean }`. | Campo | Significado | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `created` | Entradas registradas en el registro global (reenvíos idénticos no se duplican; sobre horas extra rechazadas sin checadas vinculadas, la respuesta es `409 OVERTIME_REJECTED`). | | `retros_touched` | Ajustes retroactivos creados o refrescados por fechas dentro de un periodo cerrado. | | `recalc_pending` | `true` si el recálculo en tiempo real de algún periodo abierto no se completó: la incidencia quedó registrada, pero los recibos de ese periodo van desfasados hasta volver a calcularlo. | ## Errores [#errores] | Status | Código | Causa | | ------ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | — | Cuerpo mal formado o inválido. | | `400` | `EMPLOYEE_NOT_FOUND` | Algún `employee_number` no existe en la organización. | | `400` | `NO_ACTIVE_EMPLOYMENT` | Algún empleado no tiene empleo activo o pendiente. | | `400` | `INCIDENCE_NOT_FOUND` | Sin tipo activo con ese `incidence_code` en el catálogo global. | | `400` | `RANGE_TOO_LARGE` | El rango excede 366 días. | | `400` | `NO_CHARGEABLE_DAYS` | En vacaciones pagadas o permisos, todas las fechas del rango son días de descanso o festivos para los empleados. No se captura nada. | | `400` | `DISABILITY_FOLIO_NOT_ALLOWED` | Se envió `disability_folio` con un tipo que no es incapacidad. | | `400` | `DISABILITY_FOLIO_MULTIPLE_EMPLOYEES` | Se envió `disability_folio` para más de un empleado. | | `401` | — | API key ausente o inválida. | | `403` | — | La API key no tiene el permiso **Capturar asistencia**. | | `409` | `DAY_CONFLICT` | Algún empleado ya tiene otro estatus de día en una de las fechas (los tipos de día son mutuamente excluyentes). Con `disability_folio`, también cuenta una incapacidad del mismo tipo ya capturada. No se captura nada. | | `409` | `OVERTIME_REJECTED` | Algún empleado ya tiene horas extra rechazadas de ese tipo, sin checadas vinculadas, en una de las fechas. No se captura nada; el registro rechazado se corrige en la plataforma, en **Incidencias de tiempo**: cambia sus horas para enviarlo de nuevo a autorización, o elimínalo y vuelve a capturar. | | `409` | `RETRO_CONFIRMATION_REQUIRED` | Alguna fecha cae en un periodo cerrado. Reenvía con `confirm_retro: true`. | | `409` | `DISABILITY_FOLIO_TAKEN` | El folio ya está registrado para otro empleado. No se captura nada. | | `409` | `DISABILITY_FOLIO_NOT_CONSECUTIVE` | El empleado ya tiene ese folio en fechas que no son consecutivas con las enviadas. No se captura nada. | # Listar (/docs/developers/api-reference/attendance/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de las incidencias de asistencia capturadas en tu organización. Los resultados se devuelven en formato JSON e incluyen metadatos de paginación. Ofrece filtros por razón social, tipo de incidencia, rango de fechas y búsqueda por empleado. El listado devuelve únicamente las incidencias del **registro global de asistencia**. Las incidencias capturadas dentro de una corrida específica (y que pertenecen solo a esa corrida) no aparecen aquí. Los resultados se ordenan de la fecha más reciente a la más antigua. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/attendance` ## Consulta multiples incidencias [#consulta-multiples-incidencias] *** Devuelve todas las incidencias de asistencia de la organización, paginadas de 20 en 20 y ordenadas por fecha descendente. ## Query params [#query-params] Estos son **todos** los filtros disponibles: razón social, tipo de incidencia, periodo y búsqueda por empleado. Todos son opcionales y se pueden combinar. | Parametro | Tipo | Repetible | Default | Descripcion | | --------------------- | ------------------------ | --------- | ------- | --------------------------------------------------------------------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `legal_entity_number` | integer | **Sí** | - | Filtra por la razón social del empleado. Repite el parámetro para incluir varias. | | `incidence_code` | string | **Sí** | - | Filtra por el **código del tipo de incidencia** (por ejemplo `FALTA`). Repite el parámetro para incluir varios. | | `date_from` | string (`YYYY-MM-DD`) | No | - | Incluye incidencias con fecha **mayor o igual** a esta. | | `date_to` | string (`YYYY-MM-DD`) | No | - | Incluye incidencias con fecha **menor o igual** a esta. | | `search` | string | No | - | Búsqueda por empleado: nombre (sin distinguir acentos ni mayúsculas) o número de empleado exacto. | | `source` | string | **Sí** | - | Filtra por el origen exacto de la captura. Repite el parámetro para incluir varios. | | `origin` | `internal` \| `external` | No | - | Filtra por la clasificación del origen. Se puede combinar con `source`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/attendance" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 34, "page": 1, "page_size": 20, "total_pages": 2, "next": "?page=2", "previous": null, "results": [ { "date": "2026-06-05", "employee_number": 1, "employee_name": "Jesús Muñoz Rivera", "legal_entity_number": 1, "legal_entity_name": "Comercializadora del Norte S.A. de C.V.", "incidence_code": "OVERTIME_DOUBLE_HOURS", "incidence_name": "Horas extra dobles", "unit": "hours", "quantity": 8, "notes": "turno extra", "approval_status": "pending", "source": "attendance_module", "origin": "internal", "captured_by": "Ana Martínez", "updated_by": null, "created_at": "2026-06-05T10:00:00.000Z", "updated_at": "2026-06-05T10:00:00.000Z" }, { "date": "2026-06-03", "employee_number": 1, "employee_name": "Jesús Muñoz Rivera", "legal_entity_number": 1, "legal_entity_name": "Comercializadora del Norte S.A. de C.V.", "incidence_code": "FALTA", "incidence_name": "Falta injustificada", "unit": "days", "quantity": 1, "notes": null, "approval_status": "approved", "source": "attendance_module", "origin": "internal", "captured_by": "Ana Martínez", "updated_by": null, "created_at": "2026-06-03T10:00:00.000Z", "updated_at": "2026-06-03T10:00:00.000Z" } ] } ``` ## Filtra por razon social, tipo y periodo [#filtra-por-razon-social-tipo-y-periodo] *** `legal_entity_number` e `incidence_code` son **repetibles**: envía el parámetro varias veces para filtrar por varias razones sociales o varios tipos de incidencia a la vez. El ejemplo trae las faltas **y** las vacaciones de la razón social `1` durante junio. Los códigos de incidencia disponibles los obtienes de [Tipos de incidencia → Listar](/docs/developers/api-reference/incidence-types/list). El catálogo es global y su código es único, así que cada `incidence_code` identifica un solo tipo. **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/attendance?legal_entity_number=1&incidence_code=FALTA&incidence_code=VACACIONES&date_from=2026-06-01&date_to=2026-06-30" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 2, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "date": "2026-06-10", "employee_number": 1, "employee_name": "Jesús Muñoz Rivera", "legal_entity_number": 1, "legal_entity_name": "Comercializadora del Norte S.A. de C.V.", "incidence_code": "VACACIONES", "incidence_name": "Vacaciones", "unit": "days", "quantity": 1, "notes": null, "approval_status": "approved", "source": "attendance_module", "origin": "internal", "captured_by": "Ana Martínez", "updated_by": null, "created_at": "2026-06-10T10:00:00.000Z", "updated_at": "2026-06-10T10:00:00.000Z" }, { "date": "2026-06-03", "employee_number": 1, "employee_name": "Jesús Muñoz Rivera", "legal_entity_number": 1, "legal_entity_name": "Comercializadora del Norte S.A. de C.V.", "incidence_code": "FALTA", "incidence_name": "Falta injustificada", "unit": "days", "quantity": 1, "notes": null, "approval_status": "approved", "source": "attendance_module", "origin": "internal", "captured_by": "Ana Martínez", "updated_by": null, "created_at": "2026-06-03T10:00:00.000Z", "updated_at": "2026-06-03T10:00:00.000Z" } ] } ``` ## Campos de cada fila [#campos-de-cada-fila] | Campo | Tipo | Descripcion | | --------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `date` | string (`YYYY-MM-DD`) | Día en que ocurrió la incidencia. | | `employee_number` | integer | Número del empleado dentro de la organización. | | `employee_name` | string | Nombre completo del empleado. | | `legal_entity_number` | integer | Razón social del empleado. | | `legal_entity_name` | string | Nombre de la razón social. | | `incidence_code` | string | Código del tipo de incidencia. | | `incidence_name` | string | Nombre del tipo de incidencia. | | `unit` | `"days"` \| `"hours"` \| `"count"` | Unidad del tipo de incidencia. | | `quantity` | number | Cantidad registrada. Los tipos de unidad `days` registran `1` por día, salvo las vacaciones de medio día, que registran `0.5`. | | `notes` | string \| null | Notas de la captura. | | `approval_status` | `"approved"` \| `"pending"` \| `"rejected"` | Estatus de autorización. Las horas extra dobles y triples quedan en `pending` hasta que las autorizan todos los niveles configurados, y en `rejected` si alguno las rechaza; la nómina solo paga las `approved`. Los demás tipos siempre son `approved`. | | `source` | string | Origen exacto de la captura: `attendance_module`, `payroll_run`, `employee_portal`, `api` o `time_clock`. | | `origin` | `"internal"` \| `"external"` | Clasificación del origen: `internal` cuando la capturó el equipo de nómina en la plataforma; `external` en cualquier otro caso (portal de colaborador, reloj checador, integraciones). | | `captured_by` | string \| null | Nombre de quien registró la incidencia: una persona, la integración que la envió o la plataforma. `null` si ese autor ya no existe. | | `updated_by` | string \| null | Nombre de quien la modificó por última vez. `null` si nunca se ha editado. | | `created_at` | string (ISO 8601) | Fecha de captura. | | `updated_at` | string (ISO 8601) | Última modificación. | ## Origen de la captura [#origen-de-la-captura] *** Cada incidencia registra **de dónde vino**. `source` es el origen exacto y `origin` lo resume en los dos grupos que importan para revisar una nómina: | `origin` | `source` | Significado | | ---------- | -------------------------------------- | ---------------------------------------------------------------------------- | | `internal` | `attendance_module`, `payroll_run` | La capturó el equipo de nómina dentro de la plataforma. | | `external` | `employee_portal`, `api`, `time_clock` | Llegó de fuera: portal de colaborador, una integración, o un reloj checador. | Filtra por cualquiera de los dos: `?origin=external` para revisar todo lo que entró automáticamente, o `?source=time_clock` para un origen puntual. Combinarlos los intersecta. ## Estatus de autorización [#estatus-de-autorizacion] *** Las horas extra (`OVERTIME_DOUBLE_HOURS` y `OVERTIME_TRIPLE_HOURS`) necesitan autorización antes de pagarse. `approval_status` dice en qué estatus está cada fila: | `approval_status` | Significado | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `approved` | Autorizada. La nómina la paga. Todos los tipos que no son horas extra llegan siempre así. | | `pending` | Pendiente de autorizar: falta la decisión del jefe directo, de Recursos Humanos o de los dos, según la configuración de asistencia. La nómina no la paga. | | `rejected` | Rechazada. La nómina no la paga. | Una hora extra que se autoriza después de cerrar su periodo entra como ajuste retroactivo en la siguiente nómina. Una incidencia del registro global queda identificada de forma única por la combinación `employee_number` + `date` + `incidence_code` — la misma llave que usa el endpoint de [captura](/docs/developers/api-reference/attendance/capture) para sobrescribirla. ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | Valor inválido en algún filtro (`legal_entity_number`, `date_from` o `date_to` con formato incorrecto, o `date_to` anterior a `date_from`). | | `401` | API key ausente o inválida. | # Crear (/docs/developers/api-reference/branches/create) ## Descripcion general [#descripcion-general] Este endpoint permite crear una o varias sucursales dentro de tu organización. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. El único campo obligatorio por fila es `name`; el resto son opcionales. Las sucursales pueden anidarse mediante `parent_branch_code` para formar un árbol. Una solicitud exitosa devuelve `201` con las sucursales creadas, incluyendo los campos generados por el servidor (`id`, `branch_number`, `parent_id`, `depth`, `created_at`, `updated_at`). En modo lote, si **cualquier** fila falla la validación, **ninguna** sucursal se crea — la respuesta lista los problemas por fila para corregirlos en una sola pasada. **Referencias entre filas no permitidas.** `parent_branch_code` debe resolverse contra una sucursal que **ya exista** en la base de datos. No se pueden crear referencias entre filas del mismo lote — para construir un árbol multi-nivel, envía lotes secuenciales (raíces primero, luego hijos). Las sucursales creadas sin `code` no pueden ser referenciadas como padre vía la API; si necesitas que una sucursal sea direccionable, asígnale un `code`. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/branches` ## Solicitud unica — minima [#solicitud-unica--minima] *** Envía un objeto único con `name`. Sin `parent_branch_code` la sucursal se crea como raíz (`depth: 0`). ```json { "name": "Matriz CDMX", "code": "CDMX" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/branches" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Matriz CDMX", "code": "CDMX" }' ``` **Respuesta** ```json { "id": "1a2b...", "branch_number": 1, "parent_id": null, "depth": 0, "name": "Matriz CDMX", "code": "CDMX", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T12:34:56.000Z" } ``` ## Solicitud unica — anidada bajo un padre [#solicitud-unica--anidada-bajo-un-padre] *** Para anidar una sucursal bajo otra existente, envía `parent_branch_code` con el código del padre (en mayúsculas). La API calcula `depth` y resuelve el `parent_id` por ti. ```json { "name": "Sucursal Norte", "code": "NTE", "description": "Cobertura zona norte", "parent_branch_code": "CDMX", "sort_order": 10 } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/branches" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sucursal Norte", "code": "NTE", "description": "Cobertura zona norte", "parent_branch_code": "CDMX", "sort_order": 10 }' ``` **Respuesta** ```json { "id": "5d3a...", "branch_number": 7, "parent_id": "1a2b...", "depth": 1, "name": "Sucursal Norte", "code": "NTE", "description": "Cobertura zona norte", "is_active": true, "sort_order": 10, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T12:34:56.000Z" } ``` ## Solicitud por lote (1–100 sucursales) [#solicitud-por-lote-1100-sucursales] *** Envía un **arreglo** de hasta 100 sucursales en el mismo endpoint. Si **cualquier** fila falla la validación, **ninguna** sucursal se crea y la respuesta lista los problemas por fila. ```json [ { "name": "Sucursal Norte", "code": "NTE", "parent_branch_code": "CDMX" }, { "name": "Sucursal Sur", "code": "SUR", "parent_branch_code": "CDMX" }, { "name": "Almacén GDL", "code": "GDL" } ] ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/branches" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "name": "Sucursal Norte", "code": "NTE", "parent_branch_code": "CDMX" }, { "name": "Sucursal Sur", "code": "SUR", "parent_branch_code": "CDMX" }, { "name": "Almacén GDL", "code": "GDL" } ]' ``` **Respuesta** ```json { "summary": { "total": 3, "created": 3 }, "created": [ { "id": "...", "branch_number": 7, "parent_id": "1a2b...", "depth": 1, "name": "Sucursal Norte", "code": "NTE", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T12:34:56.000Z" }, { "id": "...", "branch_number": 8, "parent_id": "1a2b...", "depth": 1, "name": "Sucursal Sur", "code": "SUR", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T12:34:56.000Z" }, { "id": "...", "branch_number": 9, "parent_id": null, "depth": 0, "name": "Almacén GDL", "code": "GDL", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T12:34:56.000Z" } ] } ``` ## Reglas de los campos [#reglas-de-los-campos] | Campo | Tipo | Requerido | Notas | | -------------------- | ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | string | Sí | No vacío tras recortar espacios. | | `code` | string | No | ≤ 60 caracteres, se almacena en mayúsculas, único por organización. Una sucursal sin `code` no puede ser usada como `parent_branch_code` por otra. | | `description` | string | No | Texto libre. | | `parent_branch_code` | string | No | Código (en mayúsculas) de una sucursal existente en la misma organización. Si se omite, la sucursal se crea como raíz. No se permiten referencias dentro del lote. | | `is_active` | boolean | No | Por defecto `true`. | | `sort_order` | integer ≥ 0 | No | Por defecto `0`. | ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — solicitud única [#201--solicitud-única] Cuando el cuerpo es **un objeto**, la respuesta es el objeto de la sucursal creada. ```json { "id": "5d3a...", "branch_number": 7, "parent_id": "1a2b...", "depth": 1, "name": "Sucursal Norte", "code": "NTE", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T12:34:56.000Z" } ``` ### `201` — solicitud por lote [#201--solicitud-por-lote] Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 3, "created": 3 }, "created": [ { "id": "...", "branch_number": 7, "code": "NTE", "...": "..." }, { "id": "...", "branch_number": 8, "code": "SUR", "...": "..." }, { "id": "...", "branch_number": 9, "code": "GDL", "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única] ```json { "message": "Validation failed.", "errors": [ { "field": "parent_branch_code", "code": "PARENT_BRANCH_NOT_FOUND", "message": "No branch with code 'XYZ' in this organization." } ] } ``` Para fallas de validación de campos (campo obligatorio faltante, tipo incorrecto), `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "name": ["Ingresa el nombre."] } } ``` ### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote] Reporte por fila. **Ninguna** sucursal del lote fue creada. ```json { "message": "Validation failed for 2 branch(es). No branches were created.", "summary": { "total": 3, "with_errors": 2 }, "errors": [ { "index": 0, "branch": { "name": "Sucursal Norte", "code": "NTE" }, "issues": [ { "field": "code", "code": "DUPLICATE_CODE", "message": "A branch with code 'NTE' already exists in this organization." } ] }, { "index": 2, "branch": { "name": "Almacén GDL", "code": "GDL" }, "issues": [ { "field": "parent_branch_code", "code": "PARENT_BRANCH_NOT_FOUND", "message": "No branch with code 'CDMX' in this organization." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `branch.name` / `branch.code` — extraídos de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. ### `409` — condición de carrera [#409--condición-de-carrera] Otra solicitud reclamó un `code` entre el momento en que validamos el tuyo y el momento en que escribimos. En modo lote, la transacción completa hace rollback — ninguna sucursal es creada. Reintenta la misma solicitud. ```json { "message": "Another request claimed a 'code' value during validation. Please retry.", "errors": [ { "field": "code", "code": "CONCURRENT_MODIFICATION", "message": "Retry the request." } ] } ``` ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos: | Codigo | Significado | | ------------------------- | -------------------------------------------------------------------------- | | `INVALID_INPUT` | Falla genérica de validación de campos (formato, longitud, tipo). | | `PARENT_BRANCH_NOT_FOUND` | `parent_branch_code` no coincide con una sucursal viva en la organización. | | `DUPLICATE_CODE` | El `code` ya existe en otra sucursal de la organización. | | `DUPLICATE_CODE_IN_BATCH` | El mismo `code` aparece en más de una fila del lote. | | `CONCURRENT_MODIFICATION` | Señal de reintento por condición de carrera (respuesta `409`). | # Eliminar (/docs/developers/api-reference/branches/delete) ## Descripcion general [#descripcion-general] Este endpoint realiza el **borrado** de la sucursal: a partir de ese momento deja de aparecer en `GET /api/v1/branches` y se trata como no encontrada en `PATCH`/`DELETE` (`404`). El borrado **no es recursivo**: si la sucursal tiene hijos vivos, la solicitud falla — primero debes eliminarlos o re-asignarlos a otro padre. Al eliminarla, la sucursal se **desvincula de cada empleo** que la referenciaba (su `branch_code` queda en `null`). No se exige un permiso granular para este endpoint todavía: basta con que la API key sea válida y esté vinculada a la organización. La respuesta exitosa es `204 No Content` con cuerpo vacío. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/branches/{branch_number}` **Path param:** `branch_number` — entero positivo, el número de sucursal dentro de la organización. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/branches/7" \ -H "Authorization: Bearer TU_API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] * Marca la sucursal como eliminada. * La fila deja de devolverse por `GET /api/v1/branches`. * Rechaza la solicitud (`400`) si la sucursal tiene **hijos vivos** — primero elimínalos o reasígnalos a otro padre con [PATCH](/docs/developers/api-reference/branches/edit). * **Desvincula** la sucursal de cada empleo que la referenciaba: su `branch_code` queda en `null` (misma transacción). ## Errores [#errores] | Status | Causa | | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `branch_number` inválido en la URL, o la sucursal tiene hijos vivos. La respuesta incluye `{ "message": "Cannot delete a branch with live children. Delete or reparent them first." }`. | | `404` | La sucursal no existe o ya fue eliminada en esta organización. | # Editar (/docs/developers/api-reference/branches/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente una sucursal. Todos los campos del cuerpo son **opcionales** y la semántica de presencia importa: * **Omitido** → el campo no se toca. * **`null`** → el campo se limpia (o se desvincula, para `parent_branch_code`). * **`""`** (string vacío) → se trata como `null`. El identificador en la URL es el `branch_number` de la sucursal dentro de tu organización. Una solicitud exitosa devuelve `200` con la forma completa de la sucursal actualizada. No se exige un permiso granular para este endpoint todavía: basta con que la API key sea válida y esté vinculada a la organización. Las sucursales eliminadas se tratan como no encontradas (`404`). ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/branches/{branch_number}` **Path param:** `branch_number` — entero positivo, el número de sucursal dentro de la organización. ## Actualizacion minima [#actualizacion-minima] *** Envía solo los campos que quieras cambiar. El resto de la sucursal queda intacto. ```json { "name": "Sucursal Norte (renombrada)" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/branches/7" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sucursal Norte (renombrada)" }' ``` **Respuesta** ```json { "id": "5d3a...", "branch_number": 7, "parent_id": "1a2b...", "path": "/1a2b.../5d3a.../", "depth": 1, "name": "Sucursal Norte (renombrada)", "code": "NTE", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T13:00:00.000Z" } ``` ## Reparentar bajo otra sucursal [#reparentar-bajo-otra-sucursal] *** Para mover una sucursal bajo otro padre, envía `parent_branch_code` con el código del nuevo padre. La API verifica que no se forme un ciclo (la sucursal no puede moverse bajo sí misma ni bajo un descendiente) y recalcula `path` y `depth` para la sucursal y **todos sus descendientes** dentro de la misma transacción. ```json { "parent_branch_code": "GDL" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/branches/7" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "parent_branch_code": "GDL" }' ``` **Respuesta** ```json { "id": "5d3a...", "branch_number": 7, "parent_id": "c9d8...", "path": "/c9d8.../5d3a.../", "depth": 1, "name": "Sucursal Norte", "code": "NTE", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T13:05:00.000Z" } ``` ## Convertir en raiz [#convertir-en-raiz] *** Para desanclar una sucursal y convertirla en raíz del árbol, envía `parent_branch_code: null`. La sucursal queda con `parent_id: null` y `depth: 0`, y los `path`/`depth` de sus descendientes se recalculan en la misma transacción. ```json { "parent_branch_code": null } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/branches/7" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "parent_branch_code": null }' ``` **Respuesta** ```json { "id": "5d3a...", "branch_number": 7, "parent_id": null, "path": "/5d3a.../", "depth": 0, "name": "Sucursal Norte", "code": "NTE", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T13:10:00.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | -------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | No vacío tras recortar. | | `code` | string \| null | `null` o `""` limpian el código. Se almacena en mayúsculas; debe ser único por organización. | | `description` | string \| null | `null` o `""` limpian la descripción. | | `parent_branch_code` | string \| null | Código (en mayúsculas) del nuevo padre. `null` convierte la sucursal en raíz. El nuevo padre debe existir y no puede ser la propia sucursal ni un descendiente (sino `400` con `CYCLE_DETECTED`). | | `is_active` | boolean | Flag activo controlado por el usuario. Independiente del borrado — una sucursal puede estar inactiva y seguir apareciendo en `GET`. Al ponerlo en `false`, se **desvincula la sucursal de cada empleo** (`branch_code → null`). | | `sort_order` | integer ≥ 0 | | ### Reparenting [#reparenting] Si `parent_branch_code` cambia: * Primero corre una **verificación de ciclos**: el nuevo padre no puede ser la propia sucursal ni un descendiente suyo. * Se actualizan `path` y `depth` de la sucursal, y se recalculan `path`/`depth` para **todos los descendientes** del subárbol en la misma transacción. ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — la sucursal actualizada: ```json { "id": "5d3a...", "branch_number": 7, "parent_id": "1a2b...", "path": "/1a2b.../5d3a.../", "depth": 1, "name": "Sucursal Norte (renombrada)", "code": "NORTE", "description": null, "is_active": false, "sort_order": 10, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T13:00:00.000Z" } ``` ## Errores [#errores] | Status | Causa | | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `branch_number` inválido en la URL; falla de validación del cuerpo; `parent_branch_code` no encontrado; intento de poner el padre en sí mismo o en un descendiente (`CYCLE_DETECTED`). | | `404` | La sucursal con ese `branch_number` no existe (o fue eliminada) en esta organización. | | `409` | El `code` enviado ya está en uso por otra sucursal de la organización. | # Listar (/docs/developers/api-reference/branches/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de las sucursales pertenecientes a tu organización. Los resultados se devuelven en formato JSON e incluyen metadatos de paginación. Cada sucursal incluye su jerarquía mediante los campos `parent_id` y `depth`. Las sucursales eliminadas se excluyen automáticamente. El tamaño de página es fijo en 20 resultados. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/branches` ## Consulta multiples sucursales [#consulta-multiples-sucursales] *** ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | --------- | --------------------- | --------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/branches" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 42, "page": 1, "page_size": 20, "total_pages": 3, "next": "https://app.integranom.com/api/v1/branches?page=2", "previous": null, "results": [ { "branch_number": 1, "parent_id": null, "depth": 0, "name": "Matriz CDMX", "code": "CDMX", "description": "Oficinas centrales", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T12:34:56.000Z" } ] } ``` ## Consulta una sucursal especifica [#consulta-una-sucursal-especifica] *** Se puede buscar una sucursal particular por su número, nombre o código. Los tres filtros hacen coincidencia exacta; como los códigos se almacenan en mayúsculas, envía siempre el código en mayúsculas. ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | --------------- | ------- | --------- | ------- | ---------------------------------------------------- | | `branch_number` | integer | No | - | Filtra por número de sucursal exacto. | | `name` | string | No | - | Filtra por nombre exacto. | | `code` | string | No | - | Filtra por código exacto (almacenado en mayúsculas). | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/branches?branch_number=1" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "branch_number": 1, "parent_id": null, "depth": 0, "name": "Matriz CDMX", "code": "CDMX", "description": "Oficinas centrales", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T12:34:56.000Z", "updated_at": "2026-05-24T12:34:56.000Z" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | --------------------------------------------- | | `400` | Valor inválido en `branch_number` o `active`. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso `getBranches`. | # Duplicar (/docs/developers/api-reference/contracts/clone) ## Descripcion general [#descripcion-general] Este endpoint crea una **copia** de un contrato bajo un número nuevo. Copia las prestaciones, los conceptos extralegales activos con su cálculo personalizado, y las razones sociales. El nombre de la copia es `" (COPIA)"`, o `" (COPIA 2)"` si ese nombre ya está en uso; los nombres se guardan en mayúsculas. La copia es un contrato **personalizado**: `template_key` queda en `null` e `is_legal_default` en `false`, sin importar el origen. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/contracts/{contract_number}/clone` **Path param:** `contract_number`: entero positivo, el número del contrato de origen dentro de la organización. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `201 Created` con el contrato nuevo completo, en la misma forma que [crear](/docs/developers/api-reference/contracts/create). **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/contracts/2/clone" \ -H "Authorization: Bearer $API_KEY" ``` **Respuesta** ```json { "contract_number": 5, "name": "INDETERMINADO (COPIA)", "contract_type": "01", "description": null, "template_key": null, "is_legal_default": false, "legal_entity_numbers": [1], "sunday_premium_rate": 0.25, "created_at": "2026-02-01T10:00:00.000Z", "updated_at": "2026-02-01T10:00:00.000Z" } ``` ## Errores [#errores] | Status | Causa | | ------ | ----------------------------------------------------------------------------------------------------------------- | | `400` | `contract_number` inválido en la URL. | | `403` | Alguna razón social del contrato queda fuera del alcance de la API key. | | `404` | El contrato de origen no existe o ya fue eliminado en esta organización. | | `409` | El nombre de la copia ya está en uso (`"Ya existe un contrato con el nombre de la copia en esta organización."`). | # Crear (/docs/developers/api-reference/contracts/create) ## Descripcion general [#descripcion-general] Este endpoint permite crear uno o varios contratos dentro de tu organización. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. Un contrato es propiedad de la organización y se comparte entre las razones sociales que indiques en `legal_entity_numbers` mediante un puente (relación M:N). El campo `contract_type` corresponde a un código SAT c\_TipoContrato (p. ej. `"01"` para indeterminado) y `concept_numbers` lista los conceptos de nómina **extralegales** que el contrato adjunta. Una solicitud única exitosa devuelve `201` con el contrato creado; una solicitud por lote devuelve `201` con `{ summary, created[] }`. Un contrato nuevo empieza con las prestaciones mínimas de ley: aguinaldo de 15 días, prima vacacional de 25 % y vacaciones según la tabla de ley. > **Nota sobre `concept_numbers`:** la API todavía no documenta cómo listar los conceptos de nómina, así que `concept_numbers` debe usar números que ya conozcas, o puedes omitirlo (arreglo vacío). ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/contracts` ## Solicitud unica [#solicitud-unica] *** Envía un objeto único con al menos `legal_entity_numbers`, `name` y `contract_type`. El contrato se crea activo por defecto y queda compartido por todas las razones sociales indicadas. Puedes omitir `concept_numbers` para crearlo sin conceptos adjuntos (arreglo vacío). ```json { "legal_entity_numbers": [1, 2], "name": "Indeterminado tiempo completo", "contract_type": "01", "concept_numbers": [7] } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/contracts" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "legal_entity_numbers": [1, 2], "name": "Indeterminado tiempo completo", "contract_type": "01", "concept_numbers": [7] }' ``` **Respuesta** ```json { "contract_number": 4, "name": "Indeterminado tiempo completo", "contract_type": "01", "description": null, "is_active": true, "sort_order": 0, "legal_entity_numbers": [1, 2], "concept_numbers": [7], "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" } ``` ## Solicitud por lote (1–100 contratos) [#solicitud-por-lote-1100-contratos] *** Envía un **arreglo** de hasta 100 contratos en el mismo endpoint; el modo lote se activa al enviar un arreglo como cuerpo. El lote es **atómico**: si **cualquier** fila falla la validación, **ningún** contrato se crea y la respuesta lista los problemas por fila. ```json [ { "legal_entity_numbers": [1], "name": "Indeterminado tiempo completo", "contract_type": "01" }, { "legal_entity_numbers": [1, 2], "name": "Por obra determinada", "contract_type": "03" } ] ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/contracts" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "legal_entity_numbers": [1], "name": "Indeterminado tiempo completo", "contract_type": "01" }, { "legal_entity_numbers": [1, 2], "name": "Por obra determinada", "contract_type": "03" } ]' ``` **Respuesta** ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "contract_number": 4, "name": "Indeterminado tiempo completo", "contract_type": "01", "description": null, "is_active": true, "sort_order": 0, "legal_entity_numbers": [1], "concept_numbers": [], "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" }, { "contract_number": 5, "name": "Por obra determinada", "contract_type": "03", "description": null, "is_active": true, "sort_order": 0, "legal_entity_numbers": [1, 2], "concept_numbers": [], "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" } ] } ``` ## Reglas de los campos [#reglas-de-los-campos] | Campo | Tipo | Requerido | Notas | | ---------------------- | ---------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `legal_entity_numbers` | arreglo de enteros positivos | Sí | Al menos 1 elemento. Las razones sociales que comparten el contrato. | | `name` | string | Sí | 3–80 caracteres, sin contar espacios al inicio o al final, que no se guardan. Único por organización. | | `contract_type` | string | Sí | Código SAT c\_TipoContrato (p. ej. `"01"`). | | `description` | string | No | ≤ 280 caracteres. | | `concept_numbers` | arreglo de enteros positivos | No | Por defecto `[]`. Conceptos extralegales a adjuntar. | | `concept_calculations` | arreglo de objetos | No | El cálculo que el contrato fija para un concepto. Cada objeto lleva `concept_number` y `calculation` (`captured`, `fixed_amount` o `formula`). Con `fixed_amount` envía `amount` (monto por periodo, mayor a cero, hasta 999,999,999,999.99 y con hasta dos decimales); con `formula` envía `formula` (texto de hasta 2000 caracteres, validado contra el vocabulario de fórmulas). Un concepto que no listas conserva su propio cálculo. El concepto debe venir también en `concept_numbers`. Cada concepto aparece una sola vez. | | `concept_values` | arreglo de objetos | No | Formato anterior de `concept_calculations`, que se sigue aceptando. Cada objeto lleva `concept_number` y `amount` (monto por periodo, mayor a cero; se redondea a dos decimales) o `rate_of_salary` (fracción del salario del periodo, mayor a cero y hasta 1), nunca ambos: un monto se guarda como cálculo `fixed_amount` y una fracción como la fórmula `var.sueldo_diario * var.dias_pagados * `. No se envía junto con `concept_calculations`. Si un concepto se repite, cuenta el último. | ### Prestaciones [#prestaciones] Todas son **opcionales** al crear: lo que omitas toma el valor de ley, y lo que envíes se valida con los mínimos de ley sobre el resultado. Sin ninguna prestación el contrato queda como el de ley (`is_legal_default` en `true`); con al menos una es personalizado (`template_key` en `null`). Las mismas claves sirven para [editar](/docs/developers/api-reference/contracts/edit). | Campo | Tipo | Notas | | ------------------------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `year_end_bonus_table` | object\[] | Renglones `{ years_from, years_to, days }` con `days` ≥ 15. Empiezan en `years_from` 0, sin huecos ni traslapes, y solo el último tiene `years_to` en `null`. No se puede enviar junto con `year_end_bonus_days`. | | `year_end_bonus_days` | integer | **Obsoleto:** utiliza `year_end_bonus_table`. Un mismo aguinaldo, ≥ 15. Al [editar](/docs/developers/api-reference/contracts/edit) solo se acepta si la tabla tiene un solo renglón. | | `year_end_bonus_discounts_absences` | booleano | Si las faltas injustificadas, los permisos y las vacaciones sin goce y las incapacidades por enfermedad general se descuentan de los días trabajados del aguinaldo y del finiquito. Por omisión es `true`; al [editar](/docs/developers/api-reference/contracts/edit), si lo omites se conserva el valor guardado. | | `vacation_table` | object\[] | Misma forma que `year_end_bonus_table`, por años cumplidos. El renglón del primer año (`years_from` 0) debe tener `days` ≥ 12. | | `vacation_premium_rate` | número | Entre 0.25 y 1. | | `vacation_premium_payment_mode` | `"on_days_taken"` \| `"on_anniversary"` \| `"not_calculated"` | Cuándo se paga la prima vacacional en todo el contrato. | | `sunday_premium_rate` | número | Entre 0.25 y 1. | | `food_voucher_enabled` | boolean | Si es `true`, son obligatorios `food_voucher_delivery_method` y exactamente uno de `food_voucher_rate` o `food_voucher_monthly_amount` (> 0). | | `food_voucher_rate` | número \| null | Entre 0 y 0.5. | | `food_voucher_monthly_amount` | número \| null | Monto fijo mensual de vales. | | `food_voucher_delivery_method` | `"card"` \| `"cash"` \| null | Método de entrega. | | `food_voucher_capped_at_imss_exempt_limit` | boolean | Limita los vales del mes al tope exento del IMSS. | | `food_voucher_grant_frequency` | `"every_run"` \| `"monthly"` | Con `"every_run"` cada nómina paga su parte del monto mensual; con `"monthly"` una sola nómina paga el mes completo. | | `food_voucher_weekly_paying_run` | `"week_1"` \| `"week_2"` \| `"week_3"` \| `"week_4"` \| `"last_week"` \| null | Semana que paga los vales. Requerido con `"monthly"`, igual que los tres siguientes; con `"every_run"` o con los vales inactivos se guardan como `null`. | | `food_voucher_semimonthly_paying_run` | `"first_half"` \| `"second_half"` \| null | Quincena que paga los vales. | | `food_voucher_biweekly_paying_run` | `"period_1"` \| `"period_2"` \| `"last_period"` \| null | Catorcena que paga los vales. | | `food_voucher_decennial_paying_run` | `"period_1"` \| `"period_2"` \| `"period_3"` \| null | Decena que paga los vales. | | `savings_fund_enabled` | boolean | Si es `true`, `savings_fund_employee_rate` (> 0) y `savings_fund_employer_rate` son obligatorios. | | `savings_fund_employee_rate` | número \| null | Entre 0 y 0.13. | | `savings_fund_employer_rate` | número \| null | Igual a la del trabajador. | | `savings_fund_withdrawal_frequency` | `"once_a_year"` \| `"twice_a_year"` \| `"more_than_twice_a_year"` \| null | Requerida cuando el fondo está activo; `null` cuando está inactivo. | ### Coherencia de conceptos [#coherencia-de-conceptos] Cada número enviado en `concept_numbers` debe corresponder a un concepto **activo** y **extralegal** de tu organización, sin importar las razones sociales del contrato. Los conceptos de ley se calculan a cada empleado y nunca se adjuntan a un contrato. Si un número no cumple estas condiciones, la fila se rechaza con `CONCEPT_NOT_FOUND`. ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — solicitud única [#201--solicitud-única] Cuando el cuerpo es **un objeto**, la respuesta es el objeto del contrato creado. ```json { "contract_number": 4, "name": "Indeterminado tiempo completo", "contract_type": "01", "description": null, "is_active": true, "sort_order": 0, "legal_entity_numbers": [1, 2], "concept_numbers": [7], "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" } ``` Cada contrato de la respuesta incluye además `template_key`, `is_legal_default`, `concept_calculations`, `concept_values` (los montos fijos y las fórmulas de fracción del salario, en el formato anterior) y todas las [prestaciones](/docs/developers/api-reference/contracts/create#prestaciones), más `year_end_bonus_discounts_absences` y `year_end_bonus_days`, que es `null` cuando la tabla de aguinaldo cambia con la antigüedad. ### `201` — solicitud por lote [#201--solicitud-por-lote] Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "contract_number": 4, "...": "..." }, { "contract_number": 5, "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única] Para fallas de validación de campos, `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "name": ["El nombre debe tener entre 3 y 80 caracteres."] } } ``` ### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote] Reporte por fila. **Ningún** contrato del lote fue creado. ```json { "message": "Validation failed for 1 contract(s). No contracts were created.", "summary": { "total": 2, "with_errors": 1 }, "errors": [ { "index": 0, "contract": { "name": "Indeterminado tiempo completo" }, "issues": [ { "field": "concept_numbers", "code": "CONCEPT_NOT_FOUND", "message": "No se encontró un concepto extralegal activo con el número '7' en la organización." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `contract.name` — extraído de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. ### `409` — nombre duplicado [#409--nombre-duplicado] Se devuelve cuando el `name` ya existe en la organización: ```json { "message": "A contract with this 'name' already exists in this organization." } ``` ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos: | Codigo | Significado | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `INVALID_INPUT` | Falla genérica de validación de campos (formato, longitud, tipo). | | `LEGAL_ENTITY_NOT_FOUND` | Un `legal_entity_number` no existe en esta organización. | | `CONCEPT_NOT_FOUND` | Un `concept_number` no corresponde a un concepto extralegal activo. | | `CONCEPT_CALCULATION_NOT_ALLOWED` | Un elemento de `concept_calculations` apunta a un concepto que no viene en `concept_numbers`, o a uno que calcula la plataforma o un crédito. | | `CONCEPT_VALUE_NOT_ALLOWED` | Un elemento de `concept_values` apunta a un concepto que no viene en `concept_numbers`. | | `INVALID_BENEFITS` | Las prestaciones combinadas no cumplen sus reglas: vacaciones del primer año ≥ 12 días, la tabla completa sin huecos, y la coherencia de vales de despensa y fondo de ahorro. Un valor fuera de rango en un solo campo responde con `errors` por campo, sin este código. | # Eliminar (/docs/developers/api-reference/contracts/delete) ## Descripcion general [#descripcion-general] Este endpoint realiza el **borrado** del contrato: a partir de ese momento deja de aparecer en `GET /api/v1/contracts` y se trata como no encontrada en `PATCH`/`DELETE` (`404`). El borrado **no procede** si el contrato tiene empleados **activos o pendientes** vinculados: primero debes reasignarlos a otro contrato. Los vínculos con las razones sociales y los conceptos extralegales se conservan. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/contracts/{contract_number}` **Path param:** `contract_number` — entero positivo, el número de contrato dentro de la organización. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/contracts/4" \ -H "Authorization: Bearer TU_API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] 1. **Rechaza** la solicitud (`409`) si el contrato tiene empleados **activos o pendientes** vinculados; el cuerpo informa cuántos empleos quedarían afectados vía `affected_count`. Reasígnalos a otro contrato antes de reintentar. 2. **Marca** el contrato como eliminado. Deja de aparecer en `GET /api/v1/contracts`. ### `409` — empleados vigentes [#409--empleados-vigentes] ```json { "message": "Cannot delete a contract with active or pending employees.", "affected_count": 3 } ``` ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------- | | `400` | `contract_number` inválido en la URL. | | `404` | El contrato no existe o ya fue eliminado en esta organización. | | `409` | El contrato tiene empleados activos o pendientes vinculados (`affected_count`). | # Editar (/docs/developers/api-reference/contracts/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente un contrato. Todos los campos del cuerpo son **opcionales**: si omites una clave, ese campo queda intacto; si envías `description` como `null`, se limpia. El identificador en la URL es el `contract_number` del contrato dentro de tu organización. Enviar `legal_entity_numbers` **reemplaza** el conjunto de razones sociales vinculadas, y enviar `concept_numbers` **reemplaza** el conjunto de conceptos extralegales. Una solicitud exitosa devuelve `200` con la forma completa del contrato actualizado. > **Nota sobre `concept_numbers`:** la API todavía no documenta cómo listar los conceptos de nómina, así que `concept_numbers` debe usar números que ya conozcas, o puedes omitirlo (arreglo vacío). ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/contracts/{contract_number}` **Path param:** `contract_number` — entero positivo, el número de contrato dentro de la organización. ## Actualizacion minima [#actualizacion-minima] *** Envía solo los campos que quieras cambiar. El resto del contrato queda intacto. ```json { "name": "Indeterminado jornada completa" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/contracts/4" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Indeterminado jornada completa" }' ``` **Respuesta** ```json { "contract_number": 4, "name": "Indeterminado jornada completa", "contract_type": "01", "description": null, "is_active": true, "sort_order": 0, "legal_entity_numbers": [1, 2], "concept_numbers": [7, 9], "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-15T11:15:32.000Z" } ``` ## Reemplazar razones sociales y conceptos [#reemplazar-razones-sociales-y-conceptos] *** Al enviar `legal_entity_numbers` se **reconcilian** los vínculos con las razones sociales: el arreglo pasa a ser el conjunto completo de RS del contrato (se agregan las nuevas y se retiran las ausentes). Al enviar `concept_numbers` se **reemplaza** el conjunto de conceptos extralegales. Si modificas las razones sociales, los conceptos vigentes se **revalidan**: cada concepto debe seguir activo y ser extralegal; de lo contrario la solicitud falla con `CONCEPT_NOT_FOUND`. ```json { "legal_entity_numbers": [1, 2, 3], "concept_numbers": [7] } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/contracts/4" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "legal_entity_numbers": [1, 2, 3], "concept_numbers": [7] }' ``` **Respuesta** ```json { "contract_number": 4, "name": "Indeterminado jornada completa", "contract_type": "01", "description": null, "is_active": true, "sort_order": 0, "legal_entity_numbers": [1, 2, 3], "concept_numbers": [7], "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-15T11:18:09.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | ---------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `legal_entity_numbers` | arreglo de enteros positivos | Al menos 1 elemento. **Reemplaza** el conjunto de razones sociales vinculadas. | | `name` | string | 3–80 caracteres, sin contar espacios al inicio o al final, que no se guardan. Único por organización. | | `contract_type` | string | Código SAT c\_TipoContrato (p. ej. `"01"`). | | `description` | string \| null | `null` limpia la descripción. | | `concept_numbers` | arreglo de enteros positivos | **Reemplaza** el conjunto de conceptos extralegales. | | `concept_calculations` | arreglo de objetos | **Reemplaza** los cálculos por concepto. Si lo omites, los conceptos que siguen en el contrato conservan su cálculo y los conceptos nuevos empiezan con el suyo. Cada objeto sigue las reglas de [crear](/docs/developers/api-reference/contracts/create) y su concepto debe quedar en el contrato. | | `concept_values` | arreglo de objetos | Formato anterior de `concept_calculations`, que se sigue aceptando con las reglas de [crear](/docs/developers/api-reference/contracts/create). Si lo envías, **reemplaza** los cálculos por concepto igual que `concept_calculations`. No se envía junto con él. | | `is_active` | boolean | Flag activo controlado por el usuario. Al ponerlo en `false` con empleados activos o pendientes, la solicitud falla con `409` e informa `affected_count`. | | Prestaciones | | Las claves de [crear](/docs/developers/api-reference/contracts/create). Lo que omitas queda intacto. | ### Prestaciones [#prestaciones] Las reglas que combinan campos (vacaciones del primer año, vales de despensa, fondo de ahorro) se validan contra el **resultado** de combinar tu solicitud con lo guardado, y responden `400` con `INVALID_BENEFITS`. Un valor fuera de rango en un solo campo responde con `errors` por campo. Por ejemplo, activar `food_voucher_enabled` exige que el contrato ya tenga, o que envíes, el porcentaje o el monto y el método de entrega. Enviar `food_voucher_monthly_amount` limpia `food_voucher_rate`, y al revés. Solo se juzgan los grupos que envías (aguinaldo, vales, fondo de ahorro). Modificar el valor de una prestación vuelve personalizado el contrato (`template_key` en `null`), igual que en el panel. ### Coherencia al reconciliar [#coherencia-al-reconciliar] Los conceptos del contrato se **revalidan en cada edición**, incluso si no modificas `legal_entity_numbers` ni `concept_numbers`. Cada concepto adjunto debe seguir activo y ser extralegal; si alguno dejó de cumplirlo (por ejemplo, se desactivó), la solicitud se rechaza con `CONCEPT_NOT_FOUND`. Si un `legal_entity_number` no existe, el error es `LEGAL_ENTITY_NOT_FOUND`; si un `concept_number` no es un concepto extralegal activo, el error es `CONCEPT_NOT_FOUND`. ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK`: el objeto completo del contrato actualizado, con sus prestaciones: ```json { "contract_number": 4, "name": "Indeterminado jornada completa", "contract_type": "01", "description": null, "is_active": true, "sort_order": 0, "legal_entity_numbers": [1, 2, 3], "concept_numbers": [7], "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-15T11:18:09.000Z" } ``` ## Errores [#errores] ### `400` — parámetro o validación [#400--parámetro-o-validación] Para `contract_number` inválido en la URL, la respuesta es `400`. Para fallas de validación del cuerpo, `errors` es un arreglo de problemas: ```json { "message": "Validation failed.", "errors": [ { "field": "concept_numbers", "code": "CONCEPT_NOT_FOUND", "message": "No se encontró un concepto extralegal activo con el número '7' en la organización." } ] } ``` Los códigos `LEGAL_ENTITY_NOT_FOUND`, `CONCEPT_NOT_FOUND`, `CONCEPT_CALCULATION_NOT_ALLOWED`, `CONCEPT_VALUE_NOT_ALLOWED`, `WORK_SCHEDULE_NOT_FOUND`, `INVALID_BENEFITS` y `YEAR_END_BONUS_TABLE_GRADED` aparecen en `errors[].code`. Un valor fuera de rango responde `400` con `errors` como objeto indexado por campo. ### `409` — desactivar con empleados vigentes [#409--desactivar-con-empleados-vigentes] Se devuelve cuando envías `is_active: false` y el contrato aún tiene empleados **activos o pendientes**. El cuerpo informa cuántos empleos quedarían afectados vía `affected_count`: ```json { "message": "Cannot deactivate a contract with active or pending employees.", "affected_count": 3 } ``` ### `409`: contrato modificado [#409-contrato-modificado] `code: "CONTRACT_CHANGED"`: otra solicitud modificó el contrato mientras se procesaba la tuya. No se guardó nada; envíala de nuevo. ### `409` — nombre duplicado [#409--nombre-duplicado] Se devuelve cuando el nuevo `name` ya existe en la organización: ```json { "message": "A contract with this 'name' already exists in this organization." } ``` ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `contract_number` inválido en la URL; falla de validación del cuerpo (`LEGAL_ENTITY_NOT_FOUND` / `CONCEPT_NOT_FOUND` / `CONCEPT_CALCULATION_NOT_ALLOWED` / `CONCEPT_VALUE_NOT_ALLOWED` / `WORK_SCHEDULE_NOT_FOUND` / `INVALID_BENEFITS` / `YEAR_END_BONUS_TABLE_GRADED`). | | `403` | Alguna razón social del contrato o de `legal_entity_numbers` queda fuera del alcance de la API key. | | `404` | El contrato con ese `contract_number` no existe (o fue eliminado) en esta organización. | | `409` | `is_active: false` con empleados activos o pendientes (`affected_count`), `name` duplicado o `CONTRACT_CHANGED`. | # Listar (/docs/developers/api-reference/contracts/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de los contratos pertenecientes a tu organización. Los resultados se devuelven en formato JSON e incluyen metadatos de paginación. Cada contrato es propiedad de la organización y se comparte entre varias razones sociales mediante un puente (relación M:N), por lo que expone el arreglo `legal_entity_numbers` con las RS vinculadas y el arreglo `concept_numbers` con los conceptos de nómina extralegales que adjunta. El campo `contract_type` corresponde a un código SAT c\_TipoContrato (p. ej. `"01"` para indeterminado). Los contratos eliminados se excluyen automáticamente. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/contracts` ## Consulta multiples contratos [#consulta-multiples-contratos] *** ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | --------- | --------------------- | --------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/contracts" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 4, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "contract_number": 4, "name": "Indeterminado tiempo completo", "contract_type": "01", "description": null, "is_active": true, "sort_order": 0, "legal_entity_numbers": [1, 2], "concept_numbers": [7, 9], "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" } ] } ``` ## Consulta contratos filtrados [#consulta-contratos-filtrados] *** Se pueden acotar los resultados por número de contrato, por tipo de contrato SAT o por razón social. Todos los filtros hacen coincidencia exacta y se pueden combinar; `legal_entity_number` devuelve los contratos vinculados a esa RS. ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | --------------------- | ------- | --------- | ------- | ------------------------------------------------------------ | | `contract_number` | integer | No | - | Filtra por número de contrato exacto. | | `contract_type` | string | No | - | Filtra por código SAT c\_TipoContrato (coincidencia exacta). | | `legal_entity_number` | integer | No | - | Devuelve los contratos vinculados a esa razón social. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/contracts?legal_entity_number=1&contract_type=01" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "contract_number": 4, "name": "Indeterminado tiempo completo", "contract_type": "01", "description": null, "is_active": true, "sort_order": 0, "legal_entity_numbers": [1, 2], "concept_numbers": [7, 9], "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" } ] } ``` Cada contrato de la respuesta incluye además `template_key`, `is_legal_default`, `concept_calculations`, `concept_values` (los montos fijos y las fórmulas de fracción del salario, en el formato anterior) y todas las [prestaciones](/docs/developers/api-reference/contracts/create#prestaciones), más `year_end_bonus_discounts_absences` y `year_end_bonus_days`, que es `null` cuando la tabla de aguinaldo cambia con la antigüedad. ## Errores [#errores] | Status | Causa | | ------ | ----------------------------------------------------------------------------------------------------------------------- | | `400` | Valor inválido en alguno de los filtros (`page`, `contract_number`, `contract_type`, `legal_entity_number` o `active`). | | `401` | API key ausente o inválida. | # Crear (/docs/developers/api-reference/departments/create) ## Descripcion general [#descripcion-general] Este endpoint permite crear uno o varios departamentos dentro de tu organización. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. El único campo obligatorio por fila es `name`; el resto son opcionales. Los departamentos pueden anidarse mediante `parent_department_number` para formar una jerarquía, y opcionalmente vincularse a un centro de costos vía `cost_center_code` o inline mediante `cost_center`. Una solicitud exitosa devuelve `201` con los departamentos creados, incluyendo los campos generados por el servidor (`id`, `department_number`, `depth`, `created_at`, `updated_at`). ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/departments` ## Solicitud unica — minima [#solicitud-unica--minima] *** Envía un objeto único con solo `name`. El departamento se crea como raíz (sin padre), sin código, sin centro de costos vinculado y activo por defecto. ```json { "name": "Recursos Humanos" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/departments" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Recursos Humanos" }' ``` **Respuesta** ```json { "id": "ck...", "department_number": 7, "parent_id": null, "depth": 0, "name": "Recursos Humanos", "code": null, "description": null, "cost_center_id": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## Solicitud unica — anidada bajo un padre [#solicitud-unica--anidada-bajo-un-padre] *** Para anidar un departamento bajo otro existente, envía `parent_department_number` con el número del padre. La API calcula `depth` y resuelve el `parent_id` por ti. ```json { "name": "Reclutamiento", "code": "RH-REC", "description": "Subárea de reclutamiento", "parent_department_number": 1, "sort_order": 10 } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/departments" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Reclutamiento", "code": "RH-REC", "description": "Subárea de reclutamiento", "parent_department_number": 1, "sort_order": 10 }' ``` **Respuesta** ```json { "id": "ck...", "department_number": 8, "parent_id": "ck...", "depth": 1, "name": "Reclutamiento", "code": "RH-REC", "description": "Subárea de reclutamiento", "cost_center_id": null, "is_active": true, "sort_order": 10, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## Solicitud unica — con centro de costos inline [#solicitud-unica--con-centro-de-costos-inline] *** Cuando el departamento se vincula a un centro de costos (vía `cost_center_code` o inline con `cost_center`), el **centro de costos es la fuente de verdad** para los siguientes campos del departamento: `name`, `code`, `description`, `is_active`, `sort_order`. Cualquier valor que el usuario envíe para esos campos se ignora silenciosamente y el departamento se escribe con los valores del CC. Observa la respuesta: aunque se envió `"name": "Ventas"` y `"code": "VEN"`, el departamento se guardó con `"name": "Centro de costos Ventas"` y `"code": "CC-VEN"` porque esos son los valores del centro de costos. ```json { "name": "Ventas", "code": "VEN", "parent_department_number": 1, "cost_center": { "code": "CC-VEN", "name": "Centro de costos Ventas", "is_active": true } } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/departments" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Ventas", "code": "VEN", "parent_department_number": 1, "cost_center": { "code": "CC-VEN", "name": "Centro de costos Ventas", "is_active": true } }' ``` **Respuesta** ```json { "id": "ck...", "department_number": 5, "parent_id": "ck...", "depth": 1, "name": "Centro de costos Ventas", "code": "CC-VEN", "description": null, "cost_center_id": "ck...", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## Solicitud por lote (1–100 departamentos) [#solicitud-por-lote-1100-departamentos] *** Envía un **arreglo** de hasta 100 departamentos en el mismo endpoint, el modo lote se activa al enviar un arreglo como cuerpo. Si **cualquier** fila falla la validación, **ningún** departamento se crea y la respuesta lista los problemas por fila. **Referencias entre filas no permitidas.** Una fila no puede usar `parent_department_number` para apuntar a otra fila del mismo lote — los padres deben existir antes de la solicitud. Para construir árboles de varios niveles, crea los nodos raíz en una solicitud y los hijos en una segunda. De la misma forma, un `cost_center` inline no puede compartirse entre filas: cada fila crea su propio CC. ```json [ { "name": "Marketing", "code": "MKT" }, { "name": "Finanzas", "code": "FIN", "parent_department_number": 1 } ] ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/departments" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "name": "Marketing", "code": "MKT" }, { "name": "Finanzas", "code": "FIN", "parent_department_number": 1 } ]' ``` **Respuesta** ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "id": "ck...", "department_number": 6, "parent_id": null, "depth": 0, "name": "Marketing", "code": "MKT", "description": null, "cost_center_id": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" }, { "id": "ck...", "department_number": 7, "parent_id": "ck...", "depth": 1, "name": "Finanzas", "code": "FIN", "description": null, "cost_center_id": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ] } ``` ## Reglas de los campos [#reglas-de-los-campos] | Campo | Tipo | Requerido | Notas | | -------------------------- | ---------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Sí | No vacío tras recortar espacios. (Sobrescrito silenciosamente si el departamento se vincula a un CC.) | | `code` | string | No | ≤ 60 caracteres, se almacena en mayúsculas, único por organización. (Sobrescrito silenciosamente si el departamento se vincula a un CC.) | | `description` | string | No | Texto libre. (Sobrescrito silenciosamente si el departamento se vincula a un CC.) | | `parent_department_number` | integer positivo | No | Debe ser el `department_number` de un departamento existente en la misma organización. No se permiten referencias dentro del lote. | | `cost_center_code` | string | No | Código de un CC existente; se almacena en mayúsculas. Mutuamente excluyente con `cost_center`. | | `cost_center` | object | No | Crea un CC nuevo y vincula este departamento a él (forma abajo). Mutuamente excluyente con `cost_center_code`. En modo lote, cada fila crea su propio CC — no se pueden compartir entre filas. | | `is_active` | boolean | No | Por defecto `true`. (Sobrescrito silenciosamente si el departamento se vincula a un CC.) | | `sort_order` | integer ≥ 0 | No | Por defecto `0`. (Sobrescrito silenciosamente si el departamento se vincula a un CC.) | ### Forma de `cost_center` (creación inline) [#forma-de-cost_center-creación-inline] | Campo | Tipo | Requerido | Notas | | ------------------------- | ----------- | --------- | -------------------------------------------------------- | | `code` | string | Sí | Se almacena en mayúsculas, ≤ 60, único por organización. | | `name` | string | Sí | No vacío tras recortar espacios. | | `description` | string | No | | | `parent_cost_center_code` | string | No | Código de un CC existente en la organización. | | `sort_order` | integer ≥ 0 | No | Por defecto `0`. | ### Espejo de campos con el centro de costos [#espejo-de-campos-con-el-centro-de-costos] Cuando el departamento se vincula a un centro de costos (al crear, sea vía `cost_center_code` o `cost_center`), el CC es dueño de estos campos en el departamento: `name`, `code`, `description`, `sort_order`. Los valores que el usuario envíe para esos campos se ignoran y el departamento se escribe con los valores del CC. ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — solicitud única [#201--solicitud-única] Cuando el cuerpo es **un objeto**, la respuesta es el objeto del departamento creado. ```json { "id": "ck...", "department_number": 5, "parent_id": "ck...", "depth": 1, "name": "Ventas", "code": "VEN", "description": null, "cost_center_id": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ### `201` — solicitud por lote [#201--solicitud-por-lote] Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "id": "ck...", "department_number": 6, "...": "..." }, { "id": "ck...", "department_number": 7, "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única] Para fallas de validación de campos, `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "name": ["Ingresa el nombre."] } } ``` ### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote] Reporte por fila. **Ningún** departamento del lote fue creado. ```json { "message": "Validation failed for 2 department(s). No departments were created.", "summary": { "total": 3, "with_errors": 2 }, "errors": [ { "index": 0, "department": { "name": "Ventas", "code": "VEN" }, "issues": [ { "field": "parent_department_number", "code": "PARENT_DEPARTMENT_NOT_FOUND", "message": "No department with number '99' in this organization." } ] }, { "index": 2, "department": { "name": "Compras", "code": "VEN" }, "issues": [ { "field": "code", "code": "DUPLICATE_CODE_IN_BATCH", "message": "Code 'VEN' appears multiple times in this batch." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `department.name` / `department.code` — extraídos de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. ### `409` — condición de carrera [#409--condición-de-carrera] Se devuelve con el código `CONCURRENT_MODIFICATION` cuando otra solicitud reclama un `code` (o modifica un CC referenciado) entre el momento en que validamos el tuyo y el momento en que escribimos. En modo lote, la transacción completa hace rollback — ningún departamento es creado. Reintenta la misma solicitud. ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos: | Codigo | Significado | | ------------------------------------- | ---------------------------------------------------------------------- | | `INVALID_INPUT` | Falla genérica de validación de campos (formato, longitud, tipo). | | `PARENT_DEPARTMENT_NOT_FOUND` | `parent_department_number` no coincide con un departamento existente. | | `COST_CENTER_NOT_FOUND` | `cost_center_code` no coincide con un CC existente. | | `COST_CENTER_PARENT_NOT_FOUND` | `cost_center.parent_cost_center_code` no coincide con un CC existente. | | `DUPLICATE_CODE` | `code` ya existe en la base de datos para esta organización. | | `DUPLICATE_CODE_IN_BATCH` | Dos o más filas del lote envían el mismo `code`. | | `DUPLICATE_COST_CENTER_CODE` | El `cost_center.code` inline ya existe en la base de datos. | | `DUPLICATE_COST_CENTER_CODE_IN_BATCH` | Dos o más filas envían el mismo `cost_center.code` inline. | | `CONCURRENT_MODIFICATION` | Señal de reintento por condición de carrera (respuesta `409`). | # Eliminar (/docs/developers/api-reference/departments/delete) ## Descripcion general [#descripcion-general] Este endpoint realiza el **borrado** del departamento: a partir de ese momento deja de aparecer en `GET /api/v1/departments` y se trata como no encontrada en `PATCH`/`DELETE` (`404`). El borrado **no es recursivo**: si el departamento tiene hijos vivos, la solicitud falla — primero debes eliminarlos o re-asignarlos a otro padre. Si el departamento está **vinculado a un centro de costos**, el CC controla su ciclo de vida y el borrado directo se rechaza con `409` — elimina o desvincula el CC. Al eliminar el departamento, se **desvincula de cada empleo** que lo referenciaba (su `department_code` queda en `null`). ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/departments/{department_number}` **Path param:** `department_number` — entero positivo, el número de departamento dentro de la organización. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/departments/5" \ -H "Authorization: Bearer TU_API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] * La fila deja de devolverse por `GET /api/v1/departments`. * **Rechaza** la solicitud (`409`) si el departamento está **vinculado a un centro de costos** — el CC controla su ciclo de vida; elimínalo o desvincúlalo primero. * **Rechaza** la solicitud (`400`) si el departamento tiene **hijos vivos** — primero elimínalos o reasígnalos a otro padre con [PATCH](/docs/developers/api-reference/departments/edit). * **Desvincula** el departamento de cada empleo que lo referenciaba: el `department_code` de esos empleos queda en `null` (dentro de la misma transacción). ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `400` | `department_number` inválido en la URL, o el departamento tiene hijos vivos. | | `409` | El departamento está vinculado a un centro de costos. Cuerpo: `{ "message": "This department is managed by a cost center. Unlink or delete the cost center instead." }`. | | `404` | El departamento no existe o ya fue eliminado en esta organización. | # Editar (/docs/developers/api-reference/departments/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente un departamento. Todos los campos del cuerpo son **opcionales**: si omites una clave, ese campo no se toca; si la envías como `null`, ese campo se limpia (cuando aplica). El identificador en la URL es el `department_number` del departamento dentro de tu organización. Una solicitud exitosa devuelve `200` con la forma completa del departamento actualizado. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/departments/{department_number}` **Path param:** `department_number` — entero positivo, el número de departamento dentro de la organización. ## Actualizacion minima [#actualizacion-minima] *** Envía solo los campos que quieras cambiar. El resto del departamento queda intacto. ```json { "name": "Ventas Internacionales" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/departments/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Ventas Internacionales" }' ``` **Respuesta** ```json { "id": "ck...", "department_number": 5, "parent_id": "ck...", "depth": 1, "name": "Ventas Internacionales", "code": "VEN", "description": null, "cost_center_id": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T11:15:32.000Z" } ``` ## Limpiar campos y desanidar [#limpiar-campos-y-desanidar] *** `null` (y `""` para strings) limpia el campo. En particular, `parent_department_number: null` desancla el departamento y lo convierte en raíz (`depth: 0`). ```json { "description": null, "code": null, "parent_department_number": null } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/departments/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "description": null, "code": null, "parent_department_number": null }' ``` **Respuesta** ```json { "id": "ck...", "department_number": 5, "parent_id": null, "depth": 0, "name": "Ventas Internacionales", "code": null, "description": null, "cost_center_id": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T11:18:09.000Z" } ``` ## Vincular a un centro de costos [#vincular-a-un-centro-de-costos] *** Al vincular un departamento a un centro de costos (sea con `cost_center_code` o inline con `cost_center`), los campos `name`, `code`, `description`, `is_active` y `sort_order` del departamento se **sobrescriben** con los del CC. Cualquier valor que envíes para esos campos en el mismo request se ignora. Para desvincular sin cambiar los valores actuales del departamento, envía `cost_center_code: null` — el departamento conserva su `name`, `code`, etc. tal como están en ese momento. ```json { "cost_center_code": "CC-VEN" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/departments/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "cost_center_code": "CC-VEN" }' ``` **Respuesta** ```json { "id": "ck...", "department_number": 5, "parent_id": "ck...", "depth": 1, "name": "Centro de costos Ventas", "code": "CC-VEN", "description": "Ventas y postventa", "cost_center_id": "ck...", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T11:22:44.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | -------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | No vacío tras recortar. | | `code` | string \| null | `null` o `""` limpian el código. | | `description` | string \| null | `null` o `""` limpian la descripción. | | `parent_department_number` | integer positivo \| null | `null` convierte el departamento en raíz. El nuevo padre debe existir y no puede ser el propio departamento ni un descendiente. | | `cost_center_code` | string \| null | Vincula a un CC existente. `null` desvincula. Mutuamente excluyente con `cost_center`. | | `cost_center` | object | Crea un CC nuevo y vincula. Misma forma que en [crear](/docs/developers/api-reference/departments/create). Mutuamente excluyente con `cost_center_code`. | | `is_active` | boolean | Flag activo controlado por el usuario. Independiente del borrado — un departamento puede estar inactivo y seguir apareciendo en `GET`. Al ponerlo en `false`, se **desvincula el departamento de cada empleo** (`department_code → null`). Mientras está vinculado a un CC, el valor enviado se ignora (el CC controla el flag). | | `sort_order` | integer ≥ 0 | | ### Espejo de campos con el centro de costos [#espejo-de-campos-con-el-centro-de-costos] Cuando el departamento queda (o ya estaba) vinculado a un centro de costos, el CC es dueño de estos campos: `name`, `code`, `description`, `is_active`, `sort_order`. Cualquier valor del caller para esos campos se ignora. * **Vinculando** (envías `cost_center_code` o `cost_center`): los campos espejo del departamento se sobrescriben con los valores del CC. * **Desvinculando** (envías `cost_center_code: null`): el departamento retiene sus valores actuales y `cost_center_id` se limpia. * **Ya vinculado, sin tocar el vínculo**: los valores enviados para los campos espejo se ignoran silenciosamente. ### Reparenting [#reparenting] Si `parent_department_number` cambia: * Primero corre una **verificación de ciclos**: el nuevo padre no puede ser el propio departamento ni un descendiente suyo. * Se actualizan `path` y `depth` del departamento, y se recalculan `path`/`depth` para **todos los descendientes** del subárbol. ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — misma forma que la respuesta de [crear](/docs/developers/api-reference/departments/create) en modo single: ```json { "id": "ck...", "department_number": 5, "parent_id": "ck...", "depth": 1, "name": "Ventas", "code": "VEN", "description": null, "cost_center_id": null, "is_active": true, "sort_order": 0, "created_at": "...", "updated_at": "..." } ``` ## Errores [#errores] | Status | Causa | | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `department_number` inválido en la URL; falla de validación del cuerpo; `parent_department_number` no encontrado; intento de poner el padre en sí mismo o en un descendiente (`CYCLE_DETECTED`); `cost_center_code` o `cost_center.parent_cost_center_code` no encontrado; `cost_center_code` y `cost_center` enviados a la vez. | | `404` | El departamento con ese `department_number` no existe (o fue eliminado) en esta organización. | | `409` | Carrera por `code` duplicado durante la escritura. | # Vincular centro de costos (/docs/developers/api-reference/departments/link-cost-center) ## Descripcion general [#descripcion-general] Este endpoint crea un **nuevo centro de costos** copiando los campos actuales del departamento (`name`, `code`, `description`, `is_active`, `sort_order`) y vincula ambos. A grandes rasgos, hace tres cosas en una sola transacción: 1. Lee los campos actuales del departamento (`name`, `code`, `description`, `is_active`, `sort_order`). 2. Crea un CC nuevo con esos valores. Si envías `code_override`, ese valor gana sobre el `code` del departamento. 3. Vincula el departamento al CC recién creado (`cost_center_id` se asigna). Si usaste `code_override`, también se actualiza el `code` del departamento para mantener la invariante de los campos espejo. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/departments/{department_number}/cost-center` **Path param:** `department_number` — entero positivo, el número de departamento dentro de la organización. ## Caso minimo — el departamento ya tiene code [#caso-minimo--el-departamento-ya-tiene-code] *** Cuando el departamento ya tiene `code`, el cuerpo puede ser un objeto vacío. El CC nuevo se crea como raíz, con los valores actuales del departamento. ```json {} ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/departments/5/cost-center" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` **Respuesta** ```json { "id": "ck...", "cost_center_number": 12, "parent_id": null, "depth": 0, "name": "Ventas", "code": "VEN", "description": "Sales root", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## Anidar el nuevo CC bajo un CC padre [#anidar-el-nuevo-cc-bajo-un-cc-padre] *** Envía `parent_cost_center_code` para colocar el CC nuevo bajo otro CC existente en el árbol de centros de costos. ```json { "parent_cost_center_code": "ROOT" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/departments/5/cost-center" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "parent_cost_center_code": "ROOT" }' ``` **Respuesta** ```json { "id": "ck...", "cost_center_number": 12, "parent_id": "ck...", "depth": 1, "name": "Ventas", "code": "VEN", "description": "Sales root", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## El departamento no tiene code — usa `code_override` [#el-departamento-no-tiene-code--usa-code_override] *** Cuando el `code` del departamento es `null`, **debes** enviar `code_override`. Ese valor se asigna al CC nuevo y también se sincroniza al departamento (para que ambos compartan el mismo `code`, como exige la invariante de los campos espejo). Sin `code_override`, la solicitud falla con `400 MISSING_CODE`. ```json { "code_override": "VEN-X" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/departments/5/cost-center" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code_override": "VEN-X" }' ``` **Respuesta** ```json { "id": "ck...", "cost_center_number": 12, "parent_id": null, "depth": 0, "name": "Ventas", "code": "VEN-X", "description": "Sales root", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Requerido | Notas | | ------------------------- | ------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `parent_cost_center_code` | string | No | Se almacena en mayúsculas. Si se envía, el CC nuevo se coloca bajo ese CC en el árbol de centros de costos. | | `code_override` | string | Condicional | **Requerido** cuando el `code` actual del departamento es `null`. Asigna el `code` al CC nuevo (y al departamento, para mantener el espejo). Máx. 60 caracteres. | ## Forma de la respuesta [#forma-de-la-respuesta] `201 Created` — el centro de costos recién creado: ```json { "id": "ck...", "cost_center_number": 12, "parent_id": "ck...", "depth": 1, "name": "Ventas", "code": "VEN", "description": "Sales root", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## Errores [#errores] | Status | Código / mensaje | Causa | | ------ | -------------------------------------------------- | ------------------------------------------------------------------------- | | `400` | `MISSING_CODE` | El `code` del departamento es `null` y no enviaste `code_override`. | | `400` | `"Invalid 'parent_cost_center_code'…"` | El `parent_cost_center_code` no existe en la organización. | | `404` | `"Department not found."` | No existe un departamento con ese `department_number` en la organización. | | `409` | `"A cost center with this 'code' already exists…"` | Carrera de duplicado en el `code` del CC al escribir. | # Listar (/docs/developers/api-reference/departments/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de los departamentos pertenecientes a tu organización. Los resultados se devuelven en formato JSON e incluyen metadatos de paginación. Cada departamento incluye su jerarquía mediante los campos `parent_id` y `depth`, y su vínculo con un centro de costos mediante `cost_center_id`. Los departamentos eliminados se excluyen automáticamente. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/departments` ## Consulta multiples departamentos [#consulta-multiples-departamentos] *** ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | --------- | --------------------- | --------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/departments" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 42, "page": 1, "page_size": 20, "total_pages": 3, "next": "https://app.integranom.com/api/v1/departments?page=2", "previous": null, "results": [ { "department_number": 1, "parent_id": null, "depth": 0, "name": "Administración", "code": "ADM", "description": "Administrative root", "cost_center_id": "ck...", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ] } ``` ## Consulta un departamento especifico [#consulta-un-departamento-especifico] *** Se puede buscar un departamento particular por su número, nombre o código. Los tres filtros hacen coincidencia exacta sin distinguir mayúsculas y minúsculas: el nombre y el código se almacenan en mayúsculas y el filtro se compara también en mayúsculas. ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | ------------------- | ------- | --------- | ------- | ---------------------------------------------------- | | `department_number` | integer | No | - | Filtra por número de departamento exacto. | | `name` | string | No | - | Filtra por nombre exacto. | | `code` | string | No | - | Filtra por código exacto (almacenado en mayúsculas). | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/departments?department_number=1" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "department_number": 1, "parent_id": null, "depth": 0, "name": "Administración", "code": "ADM", "description": "Administrative root", "cost_center_id": "ck...", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------- | | `400` | Valor inválido en `department_number` o `active`. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso `getDepartments`. | # Crear (/docs/developers/api-reference/cost-centers/create) ## Descripcion general [#descripcion-general] Este endpoint permite crear uno o varios centros de costos dentro de tu organización. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. Los campos obligatorios por fila son `code` y `name`; el resto son opcionales. Los centros de costos pueden anidarse mediante `parent_cost_center_code` para formar una jerarquía, y opcionalmente vincularse a un departamento existente vía `link_department_number` o crear uno nuevo inline mediante `department`. Una solicitud exitosa devuelve `201` con los centros de costos creados, incluyendo los campos generados por el servidor (`id`, `cost_center_number`, `parent_id`, `depth`, `created_at`, `updated_at`). ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/cost-centers` ## Solicitud unica — minima [#solicitud-unica--minima] *** Envía un objeto único con `code` y `name`. El centro de costos se crea como raíz (sin padre), sin departamento vinculado y activo por defecto. ```json { "code": "VEN-CC", "name": "Centro de costos Ventas" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/cost-centers" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "VEN-CC", "name": "Centro de costos Ventas" }' ``` **Respuesta** ```json { "id": "ck...", "cost_center_number": 5, "parent_id": null, "depth": 0, "name": "Centro de costos Ventas", "code": "VEN-CC", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## Solicitud unica — vincular un departamento existente [#solicitud-unica--vincular-un-departamento-existente] *** Usa `link_department_number` para asociar un departamento ya creado (y sin CC) al centro de costos nuevo. El departamento `5` recibe `costCenterId` apuntando al CC recién creado y sus campos `name`, `code`, `description`, `is_active`, `sort_order` se sobrescriben con los del CC. Si el departamento ya está vinculado a otro CC, la solicitud falla con `DEPARTMENT_ALREADY_LINKED`. ```json { "code": "VEN-CC", "name": "Centro de costos Ventas", "link_department_number": 5 } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/cost-centers" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "VEN-CC", "name": "Centro de costos Ventas", "link_department_number": 5 }' ``` **Respuesta** ```json { "id": "ck...", "cost_center_number": 6, "parent_id": null, "depth": 0, "name": "Centro de costos Ventas", "code": "VEN-CC", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## Solicitud unica — con departamento inline [#solicitud-unica--con-departamento-inline] *** Usa `department` para crear de una sola vez un departamento nuevo vinculado al CC. Es **mutuamente excluyente** con `link_department_number` y solo está disponible en modo single (no batch). El único campo significativo dentro de `department` es `parent_department_number`, que coloca el nuevo departamento bajo un departamento existente. Omítelo para crear un departamento raíz. Los demás campos (`name`, `code`, `description`, `is_active`, `sort_order`) se aceptan por simetría con `POST /api/v1/departments` pero se **sobrescriben silenciosamente** con los valores del CC. ```json { "code": "MKT-CC", "name": "Marketing", "department": { "parent_department_number": 1 } } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/cost-centers" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "MKT-CC", "name": "Marketing", "department": { "parent_department_number": 1 } }' ``` **Respuesta** ```json { "id": "ck...", "cost_center_number": 7, "parent_id": null, "depth": 0, "name": "Marketing", "code": "MKT-CC", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## Solicitud por lote (1–100 centros de costos) [#solicitud-por-lote-1100-centros-de-costos] *** Envía un **arreglo** de hasta 100 centros de costos en el mismo endpoint; el modo lote se activa al enviar un arreglo como cuerpo. Si **cualquier** fila falla la validación, **ningún** CC se crea y la respuesta lista los problemas por fila. **Referencias entre filas no permitidas.** Una fila no puede usar `parent_cost_center_code` para apuntar a otra fila del mismo lote — el padre debe existir antes de la solicitud. Para construir árboles de varios niveles, crea las raíces en una primera solicitud y los hijos en una segunda. El ejemplo de la derecha asume que `OPS` ya existe en la organización: si no existiera, la segunda fila fallaría con `COST_CENTER_PARENT_NOT_FOUND` y todo el lote se rechazaría. ```json [ { "code": "OPS", "name": "Operaciones" }, { "code": "ADM", "name": "Administración", "parent_cost_center_code": "OPS" } ] ``` **Departamento inline no disponible en lote.** El campo `department` solo se acepta en modo single; en lote omítelo o usa `link_department_number`. **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/cost-centers" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "code": "OPS", "name": "Operaciones" }, { "code": "ADM", "name": "Administración", "parent_cost_center_code": "OPS" } ]' ``` **Respuesta** ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "id": "ck...", "cost_center_number": 6, "parent_id": null, "depth": 0, "name": "Operaciones", "code": "OPS", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" }, { "id": "ck...", "cost_center_number": 7, "parent_id": "ck...", "depth": 1, "name": "Administración", "code": "ADM", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ] } ``` ## Reglas de los campos [#reglas-de-los-campos] | Campo | Tipo | Requerido | Notas | | ------------------------- | ---------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `code` | string | Sí | ≤ 60 caracteres, se almacena en mayúsculas, único por organización. | | `name` | string | Sí | No vacío tras recortar espacios. | | `description` | string | No | Texto libre. | | `parent_cost_center_code` | string | No | Debe ser el `code` de un CC existente en la misma organización. No se permiten referencias dentro del lote. | | `is_active` | boolean | No | Por defecto `true`. Independiente del borrado — usa `DELETE` para borrar. | | `sort_order` | integer ≥ 0 | No | Por defecto `0`. | | `link_department_number` | integer positivo | No | Número de un departamento existente y sin CC; quedará vinculado a este CC. Mutuamente excluyente con `department`. | | `department` | object | No | Crea un departamento nuevo vinculado a este CC (forma abajo). Solo en modo single. Mutuamente excluyente con `link_department_number`. | ### Forma de `department` (creación inline) [#forma-de-department-creación-inline] | Campo | Tipo | Requerido | Notas | | -------------------------- | ---------------- | --------- | ----------------------------------------------------------------------------------------------------- | | `parent_department_number` | integer positivo | No | Coloca el nuevo departamento bajo un departamento existente. Omítelo para crear un departamento raíz. | | `name` | string | No | Se ignora — el departamento hereda el `name` del CC. | | `code` | string | No | Se ignora — el departamento hereda el `code` del CC. | | `description` | string | No | Se ignora — el departamento hereda la `description` del CC. | | `is_active` | boolean | No | Se ignora — el departamento hereda el `is_active` del CC. | | `sort_order` | integer ≥ 0 | No | Se ignora — el departamento hereda el `sort_order` del CC. | ### Espejo de campos con el departamento [#espejo-de-campos-con-el-departamento] Cuando un departamento queda vinculado a un CC (vía `link_department_number` o `department`), el **CC es la fuente de verdad** para `name`, `code`, `description`, `is_active`, `sort_order` del departamento. Esto se aplica en escritura: el departamento vinculado siempre refleja los valores del CC. ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — solicitud única [#201--solicitud-única] Cuando el cuerpo es **un objeto**, la respuesta es el objeto del centro de costos creado. ```json { "id": "ck...", "cost_center_number": 5, "parent_id": null, "depth": 0, "name": "Centro de costos Ventas", "code": "VEN-CC", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ### `201` — solicitud por lote [#201--solicitud-por-lote] Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "id": "ck...", "cost_center_number": 6, "...": "..." }, { "id": "ck...", "cost_center_number": 7, "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única] Para fallas de validación de campos, `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "code": ["Ingresa el código."] } } ``` ### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote] Reporte por fila. **Ningún** centro de costos del lote fue creado. ```json { "message": "Validation failed for 2 cost center(s). No cost centers were created.", "summary": { "total": 3, "with_errors": 2 }, "errors": [ { "index": 0, "cost_center": { "name": "Ventas", "code": "VEN-CC" }, "issues": [ { "field": "link_department_number", "code": "DEPARTMENT_ALREADY_LINKED", "message": "Department '5' is already linked to another cost center." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `cost_center.name` / `cost_center.code` — extraídos de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. ### `409` — condición de carrera [#409--condición-de-carrera] Se devuelve cuando otra solicitud reclama un `code` entre el momento en que validamos el tuyo y el momento en que escribimos. En modo lote, la transacción completa hace rollback — ningún CC es creado. Reintenta la misma solicitud. ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos: | Codigo | Significado | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `INVALID_INPUT` | Falla genérica de validación de campos (formato, longitud, tipo). | | `COST_CENTER_PARENT_NOT_FOUND` | `parent_cost_center_code` no coincide con un CC existente. | | `DEPARTMENT_PARENT_NOT_FOUND` | `department.parent_department_number` no coincide con un departamento existente. | | `LINK_DEPARTMENT_NOT_FOUND` | `link_department_number` no coincide con un departamento existente. | | `DEPARTMENT_ALREADY_LINKED` | El departamento referenciado por `link_department_number` ya tiene `costCenterId`, o dos filas del lote referencian el mismo departamento. | | `DUPLICATE_CODE` | `code` ya existe en la base de datos para esta organización. | | `DUPLICATE_CODE_IN_BATCH` | Dos o más filas del lote envían el mismo `code`. | | `LINKED_DEPARTMENT_CODE_CONFLICT` | El departamento inline heredaría un `code` (el del CC) que ya está usado por otro departamento existente. | | `CONCURRENT_MODIFICATION` | Señal de reintento por condición de carrera (respuesta `409`). | # Eliminar (/docs/developers/api-reference/cost-centers/delete) ## Descripcion general [#descripcion-general] Este endpoint realiza el **borrado** del centro de costos: a partir de ese momento deja de aparecer en `GET /api/v1/cost-centers` y se trata como no encontrada en `PATCH`/`DELETE` (`404`). El borrado **no es recursivo**: si el CC tiene hijos vivos, la solicitud falla — primero debes eliminarlos o re-asignarlos a otro padre. Los empleos que referenciaban el CC se **desvinculan** (`cost_center_code → null`) y los departamentos vinculados se **desvinculan en cascada** conservando sus últimos valores espejados. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/cost-centers/{cost_center_number}` **Path param:** `cost_center_number` — entero positivo, el número de centro de costos dentro de la organización. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/cost-centers/5" \ -H "Authorization: Bearer TU_API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] Las tres acciones ocurren dentro de una **sola transacción**: 1. **Rechaza** la solicitud (`400`) si el CC tiene **hijos vivos** — primero elimínalos o reasígnalos a otro padre con [PATCH](/docs/developers/api-reference/cost-centers/edit). 2. **Desvincula** cada empleo que referenciaba el CC: su `cost_center_code` queda en `null`. Los departamentos vinculados sobreviven, por lo que los empleos conservan su `department_code`. 3. **Desvincula en cascada** cada departamento vivo vinculado: el `costCenterId` de cada departamento se pone en `null`. Los departamentos conservan los últimos valores espejados de `name`, `code`, `description`, `is_active`, `sort_order`. 4. **Marca** el CC como eliminado. Deja de aparecer en `GET /api/v1/cost-centers`. ## Errores [#errores] | Status | Causa | | ------ | ---------------------------------------------------------------------- | | `400` | `cost_center_number` inválido en la URL, o el CC tiene hijos vivos. | | `404` | El centro de costos no existe o ya fue eliminado en esta organización. | # Editar (/docs/developers/api-reference/cost-centers/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente un centro de costos. Todos los campos del cuerpo son **opcionales**: si omites una clave, ese campo no se toca; si la envías como `null`, ese campo se limpia (cuando aplica). El identificador en la URL es el `cost_center_number` del CC dentro de tu organización. Una solicitud exitosa devuelve `200` con la forma completa del centro de costos actualizado. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/cost-centers/{cost_center_number}` **Path param:** `cost_center_number` — entero positivo, el número de centro de costos dentro de la organización. ## Actualizacion minima [#actualizacion-minima] *** Envía solo los campos que quieras cambiar. El resto del centro de costos queda intacto. ```json { "name": "Centro de costos Ventas Internacionales" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/cost-centers/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Centro de costos Ventas Internacionales" }' ``` **Respuesta** ```json { "id": "ck...", "cost_center_number": 5, "parent_id": "ck...", "depth": 1, "name": "Centro de costos Ventas Internacionales", "code": "VEN-CC", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T11:15:32.000Z" } ``` ## Limpiar campos y desanidar [#limpiar-campos-y-desanidar] *** `null` (y `""` para strings) limpia el campo. En particular, `parent_cost_center_code: null` desancla el centro de costos y lo convierte en raíz (`depth: 0`). Nota: `code` **no es nullable** — es una columna requerida en la base de datos. ```json { "description": null, "parent_cost_center_code": null } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/cost-centers/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "description": null, "parent_cost_center_code": null }' ``` **Respuesta** ```json { "id": "ck...", "cost_center_number": 5, "parent_id": null, "depth": 0, "name": "Centro de costos Ventas Internacionales", "code": "VEN-CC", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T11:18:09.000Z" } ``` ## Cascada a departamentos vinculados [#cascada-a-departamentos-vinculados] *** Tras actualizar el CC, el handler corre un `updateMany` sobre **cada departamento vivo vinculado** para espejar los valores del CC en `name`, `code`, `description`, `is_active`, `sort_order`. Esto mantiene la invariante de vinculación intacta, hayas o no tocado esos campos en este PATCH. ```json { "code": "VEN-INT", "name": "Centro de costos Ventas Internacionales", "is_active": true } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/cost-centers/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "VEN-INT", "name": "Centro de costos Ventas Internacionales", "is_active": true }' ``` **Respuesta** ```json { "id": "ck...", "cost_center_number": 5, "parent_id": null, "depth": 0, "name": "Centro de costos Ventas Internacionales", "code": "VEN-INT", "description": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T11:22:44.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | ------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `code` | string | ≤ 60 caracteres, se almacena en mayúsculas. **No nullable** — es columna requerida. | | `name` | string | No vacío tras recortar. | | `description` | string \| null | `null` o `""` limpian la descripción. | | `parent_cost_center_code` | string \| null | `null` convierte el CC en raíz. El nuevo padre debe existir y no puede ser el propio CC ni un descendiente. | | `is_active` | boolean | Flag activo controlado por el usuario. Independiente del borrado — un CC puede estar inactivo y seguir apareciendo en `GET`. Al ponerlo en `false`, se **desvincula de cada empleo** el CC (`cost_center_code → null`) y el departamento (`department_code → null`) de los empleos cuyo departamento esté vinculado a este CC; además el flag se espeja a los departamentos vinculados. | | `sort_order` | integer ≥ 0 | | ### Reparenting [#reparenting] Si `parent_cost_center_code` cambia: * Primero corre una **verificación de ciclos**: el nuevo padre no puede ser el propio CC ni un descendiente suyo (devuelve `400`). * Se actualizan `path` y `depth` del CC, y se recalculan `path`/`depth` para **todos los descendientes** del subárbol, en la misma transacción. ### Vinculación de departamentos [#vinculación-de-departamentos] No se expone en `PATCH /api/v1/cost-centers/{n}`. La vinculación es **unidireccional**: los departamentos se atan a los CCs mediante los endpoints de departamentos: * `POST /api/v1/departments` con `cost_center_code` — crea un departamento ya vinculado. * `PATCH /api/v1/departments/{n}` con `cost_center_code` — vincula o desvincula (con `null`) un departamento existente. * `POST /api/v1/departments/{n}/cost-center` — genera un CC nuevo a partir de un departamento existente. ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — misma forma que la respuesta de [crear](/docs/developers/api-reference/cost-centers/create) en modo single: ```json { "id": "ck...", "cost_center_number": 5, "parent_id": null, "depth": 0, "name": "Centro de costos Ventas", "code": "VEN-CC", "description": null, "is_active": true, "sort_order": 0, "created_at": "...", "updated_at": "..." } ``` ## Errores [#errores] | Status | Causa | | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `cost_center_number` inválido en la URL; falla de validación del cuerpo; `parent_cost_center_code` no encontrado; intento de poner el padre en sí mismo o en un descendiente. | | `404` | El centro de costos con ese `cost_center_number` no existe (o fue eliminado) en esta organización. | | `409` | Carrera por `code` duplicado durante la escritura. | # Listar (/docs/developers/api-reference/cost-centers/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de los centros de costos pertenecientes a tu organización. Los resultados se devuelven en formato JSON e incluyen metadatos de paginación. Cada centro de costos incluye su jerarquía mediante los campos `parent_id` y `depth`. Los centros de costos eliminados se excluyen automáticamente. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/cost-centers` ## Consulta multiples centros de costos [#consulta-multiples-centros-de-costos] *** ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | --------- | --------------------- | --------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/cost-centers" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 18, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "cost_center_number": 1, "parent_id": null, "depth": 0, "name": "Operaciones", "code": "OPS", "description": "Operations root", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ] } ``` ## Consulta un centro de costos especifico [#consulta-un-centro-de-costos-especifico] *** Se puede buscar un centro de costos particular por su número, nombre o código. Los tres filtros hacen coincidencia exacta; `code` distingue mayúsculas y minúsculas en la entrada — como los códigos se almacenan en mayúsculas, envía siempre el código en mayúsculas. ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | -------------------- | ------- | --------- | ------- | ---------------------------------------------------- | | `cost_center_number` | integer | No | - | Filtra por número de centro de costos exacto. | | `name` | string | No | - | Filtra por nombre exacto. | | `code` | string | No | - | Filtra por código exacto (almacenado en mayúsculas). | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/cost-centers?cost_center_number=1" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "cost_center_number": 1, "parent_id": null, "depth": 0, "name": "Operaciones", "code": "OPS", "description": "Operations root", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | -------------------------------------------------- | | `400` | Valor inválido en `cost_center_number` o `active`. | | `401` | API key ausente o inválida. | # Registrar (/docs/developers/api-reference/employee-credits/create) ## Descripcion general [#descripcion-general] Registra créditos de empleado con las mismas reglas que el formulario de la plataforma. Acepta **un objeto** o un **arreglo de hasta 100**. El arreglo es **todo o nada**: si una fila falla, no se registra ningún crédito y la respuesta lista los problemas fila por fila. El `ordinal` no se envía: la plataforma asigna el siguiente número entre los créditos del mismo tipo del empleado. El crédito se registra en el empleo **activo o pendiente** más reciente del empleado. Requiere que la API key tenga el permiso **Registrar créditos de empleado**. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/employee-credits` ## Registra un crédito [#registra-un-crédito] *** Un objeto responde `201` con el crédito creado, con los mismos campos que el [listado](/docs/developers/api-reference/employee-credits/list). Un arreglo responde `201` con `summary` y `created`. ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Requerido | Notas | | ---------------------- | --------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `employee_number` | integer | Sí | Número del empleado. | | `credit_type` | string | Sí | `infonavit`, `fonacot`, `internal_loan`, `alimony`, `savings_pool` u `other`. | | `payroll_concept_code` | string | Sí | Concepto de deducción de tipo crédito que corresponde al `credit_type`. | | `folio` | string | No | Número del aviso o contrato. Un folio no puede repetirse entre empleados para el mismo tipo. | | `discount_type` | string | Sí | `percentage`, `fixed_amount` o `vsm`. | | `discount_value` | number | Sí | Mayor a cero. En porcentajes, fracción (`0.30` es 30%). | | `discount_basis` | string | Sí | `monthly` o `per_period`. | | `installments` | integer | No | Préstamos en plazos (`internal_loan`, `other`). Requiere `total_amount` y base `per_period`; `discount_value` se recalcula como total entre plazos. | | `term_months` | integer | FONACOT | Plazo en meses. Debe cuadrar con `discount_value` y `total_amount`. | | `total_amount` | number | Según tipo | Requerido en FONACOT y préstamos en plazos. Prohibido en INFONAVIT y pensión. | | `alimony_base` | string | Pensión | Base de la pensión alimenticia. | | `base_concept_codes` | string\[] | Pensión | Solo con `alimony_base: "pct_of_selected_concepts"`: códigos de percepciones. | | `beneficiary` | object | Pensión | `first_name` y `paternal_last_name` requeridos; `maternal_last_name`, `street`, `neighborhood`, `city`, `postal_code`, `bank`, `account_number` opcionales. | | `start_date` | string (`YYYY-MM-DD`) | Sí | Fecha de inicio del descuento. | | `document_ref` | string | No | Referencia del documento. | | `court_order_text` | string | No | Solo pensión: texto del oficio. | | `notes` | string | No | Máximo 500 caracteres. | **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employee-credits" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "employee_number": 142, "credit_type": "fonacot", "payroll_concept_code": "D012", "folio": "FON-99812", "discount_type": "fixed_amount", "discount_value": 500, "discount_basis": "monthly", "term_months": 6, "total_amount": 3000, "start_date": "2026-09-01" }' ``` **Respuesta** ```json { "employee_number": 142, "credit_type": "fonacot", "ordinal": 1, "folio": "FON-99812", "payroll_concept_code": "D012", "discount_value": 500, "term_months": 6, "total_amount": 3000, "status": "active", "start_date": "2026-09-01T00:00:00.000Z" } ``` ## Reglas por tipo [#reglas-por-tipo] | Tipo | Reglas | | ------------------------ | --------------------------------------------------------------------------------------- | | `infonavit` | Sin `total_amount`: el instituto no informa el saldo. | | `fonacot` | `fixed_amount`, base `monthly`, con `term_months` y `total_amount` que cuadren. | | `internal_loan`, `other` | `fixed_amount`. En plazos: `installments` + `total_amount` y base `per_period`. | | `savings_pool` | `percentage` o `fixed_amount`. | | `alimony` | `percentage`, con `alimony_base` y `beneficiary`. Un porcentaje no puede pasar de 100%. | ## Errores [#errores] | Status | Código | Causa | | ------ | ---------------------- | ----------------------------------------------------------------------- | | `400` | `INVALID_INPUT` | Alguna fila no cumple las reglas del tipo. El campo va en `field`. | | `400` | `NO_ACTIVE_EMPLOYMENT` | El empleado no existe o no tiene empleo activo o pendiente. | | `400` | `INVALID_CONCEPT` | El concepto no existe, está inactivo o no corresponde al `credit_type`. | | `400` | `NOT_FOUND` | Algún código de `base_concept_codes` no es una percepción activa. | | `401` | | API key ausente o inválida. | | `403` | | La API key no tiene el permiso **Registrar créditos de empleado**. | | `409` | `DUPLICATE_FOLIO` | Otro empleado ya tiene un crédito de ese tipo con ese folio. | # Consultar (/docs/developers/api-reference/employee-credits/detail) ## Descripcion general [#descripcion-general] Devuelve un crédito con los mismos campos que el [listado](/docs/developers/api-reference/employee-credits/list), más `bimesters`: lo que se retuvo en cada bimestre, que es el ciclo en que INFONAVIT y FONACOT se declaran y se pagan. Requiere que la API key tenga el permiso **Listar créditos de empleado**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/employee-credits/{employee_number}/{credit_type}/{ordinal}` ## Path params [#path-params] *** | Parametro | Descripcion | | ----------------- | --------------------------------------------------------- | | `employee_number` | Número del empleado dentro de la organización. | | `credit_type` | Tipo del crédito. | | `ordinal` | Número del crédito entre los del mismo tipo del empleado. | ## Retenido por bimestre [#retenido-por-bimestre] | Campo | Descripcion | | ----------- | --------------------------------------------------------------------- | | `retained` | Lo que se descontó en los periodos cerrados del bimestre. | | `scheduled` | Lo que correspondía descontar. | | `shortfall` | Lo que no se pudo retener (sueldo insuficiente o crédito suspendido). | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/employee-credits/142/infonavit/1" \ -H "Authorization: Bearer $API_KEY" ``` **Respuesta (extracto)** ```json { "employee_number": 142, "credit_type": "infonavit", "ordinal": 1, "discount_type": "vsm", "discount_value": 18.5, "total_amount": null, "status": "active", "bimesters": [ { "year": 2026, "bimester": 4, "retained": 4210.35, "scheduled": 4210.35, "shortfall": 0 }, { "year": 2026, "bimester": 5, "retained": 3900.1, "scheduled": 4210.35, "shortfall": 310.25 } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | ---------------------------------------------------------------------------------------------------------------- | | `400` | Algún parámetro de la URL es inválido. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Listar créditos de empleado**. | | `404` | El crédito no existe en esta organización, o el `credit_type` de la URL es el tipo retirado `infonavit_pending`. | # Actualizar (/docs/developers/api-reference/employee-credits/edit) ## Descripcion general [#descripcion-general] Actualiza un crédito existente. Envía solo los campos que cambian. El tipo, el ordinal y el folio no se pueden cambiar: identifican al crédito. Requiere que la API key tenga el permiso **Actualizar créditos de empleado**. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/employee-credits/{employee_number}/{credit_type}/{ordinal}` ## Campos del cuerpo [#campos-del-cuerpo] *** | Campo | Tipo | Notas | | -------------------------------------------------------- | --------------- | --------------------------------------------------------------------------------------------------- | | `status` | string | `active`, `suspended`, `settled` o `cancelled`. Pasar a `settled` registra la fecha de liquidación. | | `discount_type`, `discount_value`, `discount_basis` | | Mecánica del descuento. FONACOT siempre es `monthly`. | | `total_amount` | number \| null | Prohibido en INFONAVIT. En un préstamo en plazos, recalcula el descuento por pago. | | `term_months` | integer \| null | Solo FONACOT. Cambiar el plazo recalcula el total, y cambiar el total recalcula el plazo. | | `alimony_base`, `base_concept_codes`, `court_order_text` | | Solo pensión. `base_concept_codes` reemplaza la lista completa. | | `end_date` | string \| null | `YYYY-MM-DD`. | | `document_ref`, `notes` | string \| null | | **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employee-credits/142/fonacot/1" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "suspended" }' ``` **Respuesta (extracto)** ```json { "employee_number": 142, "credit_type": "fonacot", "ordinal": 1, "status": "suspended" } ``` ## Errores [#errores] | Status | Causa | | ------ | ---------------------------------------------------------------------------------------------------------------- | | `400` | Parámetro de la URL inválido, cuerpo vacío o un campo que no aplica al tipo del crédito. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Actualizar créditos de empleado**. | | `404` | El crédito no existe en esta organización, o el `credit_type` de la URL es el tipo retirado `infonavit_pending`. | # Listar (/docs/developers/api-reference/employee-credits/list) ## Descripcion general [#descripcion-general] Este endpoint devuelve un listado paginado de los **créditos de empleado**: INFONAVIT, FONACOT, préstamos internos, pensiones alimenticias, caja de ahorro y otros descuentos. Un crédito se identifica por la terna `employee_number`, `credit_type` y `ordinal`. El `ordinal` distingue los créditos del mismo tipo que un empleado tiene al mismo tiempo (por ejemplo, dos créditos FONACOT) y es el número que aparece en el recibo ("Abono préstamo 2"). Requiere que la API key tenga el permiso **Listar créditos de empleado**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/employee-credits` ## Consulta los créditos [#consulta-los-créditos] *** Devuelve los créditos paginados de 20 en 20, ordenados por tipo y ordinal. Los filtros se combinan. ## Query params [#query-params] | Parametro | Tipo | Default | Descripcion | | ----------------- | ----------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `page` | integer ≥ 1 | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `employee_number` | integer | - | Solo los créditos de un empleado. | | `credit_type` | string | - | `infonavit`, `fonacot`, `internal_loan`, `alimony`, `savings_pool` u `other`. El tipo retirado `infonavit_pending` se acepta y devuelve una lista vacía. | | `status` | string | - | `active`, `suspended`, `settled` o `cancelled`. | | `folio` | string | - | Folio exacto del crédito. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/employee-credits?employee_number=142&status=active" \ -H "Authorization: Bearer $API_KEY" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "employee_number": 142, "credit_type": "fonacot", "ordinal": 1, "folio": "FON-99812", "payroll_concept_number": 12, "payroll_concept_code": "D012", "payroll_concept_name": "Crédito FONACOT", "discount_type": "fixed_amount", "discount_value": 500, "discount_basis": "monthly", "installments": null, "term_months": 6, "alimony_base": null, "base_concept_codes": [], "beneficiary": null, "total_amount": 3000, "is_negotiated_amount": false, "status": "active", "start_date": "2026-09-01T00:00:00.000Z", "end_date": null, "settled_at": null, "document_ref": null, "court_order_text": null, "notes": null, "created_at": "2026-09-01T16:10:00.000Z", "updated_at": "2026-09-01T16:10:00.000Z" } ] } ``` ## Campos de cada fila [#campos-de-cada-fila] | Campo | Tipo | Descripcion | | ------------------------------------------------------------------------ | ------------------------ | --------------------------------------------------------------------------------------------- | | `employee_number`, `credit_type`, `ordinal` | integer, string, integer | La terna que identifica al crédito. | | `folio` | string \| null | Número del aviso o del contrato del crédito. | | `payroll_concept_number`, `payroll_concept_code`, `payroll_concept_name` | integer, string, string | Concepto de deducción que emite la línea del recibo. | | `discount_type` | string | `percentage`, `fixed_amount` o `vsm`. | | `discount_value` | number | Monto, porcentaje (fracción: `0.30` es 30%) o veces salario mínimo, según `discount_type`. | | `discount_basis` | string | `monthly` (se reparte entre los periodos del mes) o `per_period`. | | `installments` | integer \| null | Préstamos en plazos: número de pagos. `discount_value` es `total_amount` entre los plazos. | | `term_months` | integer \| null | FONACOT: plazo en meses. | | `alimony_base` | string \| null | Pensión alimenticia: base sobre la que se calcula. | | `base_concept_codes` | string\[] | Pensión con base `pct_of_selected_concepts`: códigos de las percepciones que nombra la orden. | | `beneficiary` | object \| null | Pensión alimenticia: a quién se entrega el descuento. | | `total_amount` | number \| null | Monto total del crédito. Siempre `null` en INFONAVIT, que no informa el saldo. | | `is_negotiated_amount` | boolean | Si el monto se negoció fuera del aviso original. | | `status` | string | `active`, `suspended`, `settled` o `cancelled`. | | `start_date`, `end_date`, `settled_at` | string \| null | Inicio, fin y fecha de liquidación. | | `document_ref`, `court_order_text`, `notes` | string \| null | Referencia del documento, texto del oficio (pensión) y notas. | ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------- | | `400` | Filtro inválido (`employee_number` no entero, `credit_type` o `status` fuera del catálogo). | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Listar créditos de empleado**. | # Calendario de pagos (/docs/developers/api-reference/employee-credits/schedule) ## Descripcion general [#descripcion-general] Devuelve el calendario de un crédito: lo que se esperaba descontar contra lo que se retuvo en las nóminas cerradas. Solo lo tienen los créditos con plazo: * **FONACOT**, mes por mes (`grain: "month"`), para cuadrar contra el estado de cuenta del instituto. Un mes suspendido no consume plazo: recorre el fin del crédito. * **Préstamos en plazos**, pago por pago (`grain: "installment"`). INFONAVIT, pensiones y créditos capturados sin plazo responden `409`. Requiere que la API key tenga el permiso **Listar créditos de empleado**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/employee-credits/{employee_number}/{credit_type}/{ordinal}/schedule` ## Campos [#campos] *** | Campo | Descripcion | | ----------------------------------------------------- | -------------------------------------------------------------------- | | `grain` | `month` (FONACOT) o `installment` (préstamo en plazos). | | `term_months`, `suspended_months` | Solo `month`: plazo acordado y meses suspendidos hasta hoy. | | `installments`, `paid_installments` | Solo `installment`: plazos acordados y pagos cubiertos por completo. | | `total_expected`, `total_applied`, `total_difference` | Sumas de las filas. | | `balance_remaining` | Lo que falta por pagar del total. | | `rows[].key` | El mes (`YYYY-MM`) o el número de pago. | | `rows[].status` | `applied`, `partial`, `suspended` o `pending`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/employee-credits/142/fonacot/1/schedule" \ -H "Authorization: Bearer $API_KEY" ``` **Respuesta (extracto)** ```json { "employee_number": 142, "credit_type": "fonacot", "ordinal": 1, "grain": "month", "term_months": 6, "suspended_months": 0, "installments": null, "paid_installments": null, "total_expected": 3000, "total_applied": 1000, "total_difference": 0, "balance_remaining": 2000, "rows": [ { "key": "2026-09", "expected": 500, "applied": 500, "difference": 0, "status": "applied" }, { "key": "2026-10", "expected": 500, "applied": 500, "difference": 0, "status": "applied" }, { "key": "2026-11", "expected": 500, "applied": 0, "difference": 500, "status": "pending" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | ---------------------------------------------------------------------------------------------------------------- | | `400` | Algún parámetro de la URL es inválido. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Listar créditos de empleado**. | | `404` | El crédito no existe en esta organización, o el `credit_type` de la URL es el tipo retirado `infonavit_pending`. | | `409` | El crédito no tiene plazo, así que no tiene calendario. | # Crear (/docs/developers/api-reference/employer-registrations/create) ## Descripcion general [#descripcion-general] Este endpoint permite crear uno o varios registros patronales (IMSS) dentro de tu organización. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. Cada fila debe enviar `legal_entity_number`, `number`, `risk_class`, `risk_premium` y `premium_effective_from`. Una solicitud exitosa devuelve `201` con los registros creados, incluyendo los campos generados por el servidor (`employer_registration_number`, `created_at`, `updated_at`). En modo lote, si **cualquier** fila falla la validación, **ningún** registro se crea — la respuesta lista los problemas por fila para corregirlos en una sola pasada. Un lote puede mezclar registros de varias razones sociales: la unicidad se aplica por par `(legal_entity_number, number)`, por lo que dos filas con el mismo `number` pero distinta `legal_entity_number` son válidas. `risk_premium` se acepta como número JSON (`0–1`) en el cuerpo, pero se serializa como **string decimal** (`Decimal(9,7)`, por ejemplo `"0.0054355"`) en la respuesta para preservar la precisión. **Inicio del audit trail de prima.** En cada creación exitosa se abre una fila inicial en `premium_history` con `effective_from = premium_effective_from` y `effective_to = null`. Este trail es la fuente para los archivos SUA, así que es **append-only** — nunca se edita. Los cambios posteriores de `risk_class` / `risk_premium` agregan nuevas filas vía `PATCH`. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/employer-registrations` ## Solicitud unica — minima [#solicitud-unica--minima] *** Envía los cinco campos obligatorios. `label` queda en `null` y `is_active` toma `true` por defecto. ```json { "legal_entity_number": 1, "number": "Y6712345678", "risk_class": 1, "risk_premium": 0.005, "premium_effective_from": "2026-01-01" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employer-registrations" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "legal_entity_number": 1, "number": "Y6712345678", "risk_class": 1, "risk_premium": 0.005, "premium_effective_from": "2026-01-01" }' ``` **Respuesta** ```json { "employer_registration_number": 5, "legal_entity_number": 1, "number": "Y6712345678", "label": null, "risk_class": 1, "risk_premium": "0.005", "premium_effective_from": "2026-01-01T00:00:00.000Z", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ## Solicitud unica — todos los campos [#solicitud-unica--todos-los-campos] *** Con `label` para referencia interna y `is_active` explícito. Observa cómo `risk_premium` se envía como número (`0.005`) y se devuelve como string decimal (`"0.005"`), y `premium_effective_from` se devuelve como timestamp ISO 8601. ```json { "legal_entity_number": 1, "number": "Y6712345678", "label": "RP principal CDMX", "risk_class": 1, "risk_premium": 0.005, "premium_effective_from": "2026-01-01", "is_active": true } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employer-registrations" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "legal_entity_number": 1, "number": "Y6712345678", "label": "RP principal CDMX", "risk_class": 1, "risk_premium": 0.005, "premium_effective_from": "2026-01-01", "is_active": true }' ``` **Respuesta** ```json { "employer_registration_number": 5, "legal_entity_number": 1, "number": "Y6712345678", "label": "RP principal CDMX", "risk_class": 1, "risk_premium": "0.005", "premium_effective_from": "2026-01-01T00:00:00.000Z", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ## Solicitud por lote (1–100 registros) [#solicitud-por-lote-1100-registros] *** Envía un **arreglo** de hasta 100 registros en el mismo endpoint. Si **cualquier** fila falla la validación, **ningún** registro se crea y la respuesta lista los problemas por fila. Un lote puede mezclar registros de distintas razones sociales — la unicidad se aplica por par `(legal_entity_number, number)`. ```json [ { "legal_entity_number": 1, "number": "Y6712345678", "risk_class": 1, "risk_premium": 0.005, "premium_effective_from": "2026-01-01" }, { "legal_entity_number": 2, "number": "Y8898765432", "risk_class": 3, "risk_premium": 0.02513, "premium_effective_from": "2026-01-01" } ] ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employer-registrations" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "legal_entity_number": 1, "number": "Y6712345678", "risk_class": 1, "risk_premium": 0.005, "premium_effective_from": "2026-01-01" }, { "legal_entity_number": 2, "number": "Y8898765432", "risk_class": 3, "risk_premium": 0.02513, "premium_effective_from": "2026-01-01" } ]' ``` **Respuesta** ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "employer_registration_number": 6, "legal_entity_number": 1, "number": "Y6712345678", "label": null, "risk_class": 1, "risk_premium": "0.005", "premium_effective_from": "2026-01-01T00:00:00.000Z", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" }, { "employer_registration_number": 7, "legal_entity_number": 2, "number": "Y8898765432", "label": null, "risk_class": 3, "risk_premium": "0.02513", "premium_effective_from": "2026-01-01T00:00:00.000Z", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Reglas de los campos [#reglas-de-los-campos] | Campo | Tipo | Requerido | Notas | | ------------------------ | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `legal_entity_number` | integer positivo | Sí | Debe corresponder a una razón social existente en la misma organización. | | `number` | string | Sí | Número IMSS del registro patronal. Máx. 40 caracteres. Único por razón social (no por organización). | | `label` | string | No | Etiqueta libre para referencia interna. | | `risk_class` | integer 1–5 | Sí | Clase de riesgo IMSS vigente. | | `risk_premium` | number 0–1 | Sí | Prima IMSS como fracción (ej. `0.05448` para 5.448%). Se almacena como `Decimal(9,7)`, siete decimales de fracción (cinco del porcentaje que publica el IMSS), y se devuelve como string decimal. Con más de 7 decimales se rechaza. | | `premium_effective_from` | string `YYYY-MM-DD` | Sí | Fecha de entrada en vigor de la clase/prima. Abre la fila inicial del audit trail (`premium_history`). | | `is_active` | boolean | No | Por defecto `true`. | ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — solicitud única [#201--solicitud-única] Cuando el cuerpo es **un objeto**, la respuesta es el objeto del registro creado. ```json { "employer_registration_number": 5, "legal_entity_number": 1, "number": "Y6712345678", "label": "RP principal CDMX", "risk_class": 1, "risk_premium": "0.005", "premium_effective_from": "2026-01-01T00:00:00.000Z", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ### `201` — solicitud por lote [#201--solicitud-por-lote] Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "employer_registration_number": 6, "...": "..." }, { "employer_registration_number": 7, "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única] Para fallas de validación de esquema (campo faltante, tipo incorrecto), `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "risk_premium": ["La prima debe expresarse como fracción (0–1)."] } } ``` ### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote] Reporte por fila. **Ningún** registro del lote fue creado. ```json { "message": "Validation failed for 1 employer registration(s). No registrations were created.", "summary": { "total": 2, "with_errors": 1 }, "errors": [ { "index": 0, "employer_registration": { "legal_entity_number": 99, "number": "Y6712345678" }, "issues": [ { "field": "legal_entity_number", "code": "LEGAL_ENTITY_NOT_FOUND", "message": "No legal entity with number '99' in this organization." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `employer_registration.legal_entity_number` / `employer_registration.number` — extraídos de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. ### `409` — condición de carrera [#409--condición-de-carrera] Otra solicitud reclamó el par `(legal_entity_id, number)` entre el momento en que validamos el tuyo y el momento en que escribimos. En modo lote, la transacción completa hace rollback — ningún registro es creado. Reintenta la misma solicitud. ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos: | Codigo | Significado | | --------------------------- | --------------------------------------------------------------------------------------------------- | | `INVALID_INPUT` | Falla genérica de validación de esquema (campo faltante, tipo incorrecto, fecha mal formada, etc.). | | `LEGAL_ENTITY_NOT_FOUND` | `legal_entity_number` no coincide con una razón social viva de la organización. | | `DUPLICATE_NUMBER` | Ya existe un registro patronal con ese `number` para la razón social indicada. | | `DUPLICATE_NUMBER_IN_BATCH` | Dos o más filas del lote envían el mismo par `(legal_entity_number, number)`. | | `CONCURRENT_MODIFICATION` | Señal de reintento por condición de carrera (respuesta `409`). | # Eliminar (/docs/developers/api-reference/employer-registrations/delete) ## Descripcion general [#descripcion-general] Este endpoint realiza el **borrado** del registro patronal: a partir de ese momento deja de aparecer en `GET /api/v1/employer-registrations` y se trata como no encontrada en `PATCH`/`DELETE` (`404`). La respuesta exitosa es `204 No Content` con cuerpo vacío. **No se puede eliminar mientras tenga empleos vinculados.** Si algún empleo (de cualquier estatus, **incluidos los dados de baja**) referencia este registro patronal, el borrado se rechaza con `409` — primero debes reasignar esos empleos por integridad histórica/SAT. **El historial se conserva.** Los datos históricos asociados al registro patronal no se borran, para mantener consistentes las declaraciones y reportes previos. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/employer-registrations/{employer_registration_number}` **Path param:** `employer_registration_number` — entero positivo, el número del registro patronal dentro de la organización. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/employer-registrations/5" \ -H "Authorization: Bearer TU_API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] * **Rechaza** con `409` si algún empleo referencia este registro patronal (cualquier estatus) — reasígnalos primero. * Marca el registro patronal como eliminado. * La fila deja de devolverse por `GET /api/v1/employer-registrations`. * El historial asociado al registro patronal se conserva. ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `employer_registration_number` inválido en la URL. | | `404` | El registro patronal no existe o ya fue eliminado en esta organización. | | `409` | El registro patronal sigue referenciado por uno o más empleos. Cuerpo: `{ "message": "This employer registration is still referenced by one or more employments. Reassign them before deleting it." }`. | # Editar (/docs/developers/api-reference/employer-registrations/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente un registro patronal. Todos los campos del cuerpo son **opcionales**: si omites una clave, ese campo no se toca; si la envías como `null`, ese campo se limpia (cuando aplica). El identificador en la URL es el `employer_registration_number` del registro dentro de tu organización. Una solicitud exitosa devuelve `200` con la forma completa del registro actualizado. Los registros eliminados se tratan como no encontrados (`404`). **`legal_entity_number` no se acepta.** Los registros patronales están atados a una razón social por reglas del IMSS; re-asignarlos invalidaría las declaraciones SUA históricas. La razón social asignada al crear el registro es **inmutable** durante toda su vida. **Audit trail de prima en PATCH.** Si cambia cualquiera de `risk_class`, `risk_premium` o `premium_effective_from` respecto al valor actual, el handler: 1. Cierra la fila abierta del historial poniendo `effective_to = `. 2. Abre una nueva fila con `effective_from = ` y `effective_to = null`. Ambas operaciones ocurren dentro de la misma transacción que la actualización de campos — el audit trail nunca queda fuera de sincronía con el estado actual del registro. Por eso `premium_effective_from` es **obligatorio** cada vez que envíes un cambio de `risk_class` o `risk_premium`. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/employer-registrations/{employer_registration_number}` **Path param:** `employer_registration_number` — entero positivo, el número del registro patronal dentro de la organización. ## Actualizacion minima [#actualizacion-minima] *** Cambia un campo no relacionado con la prima (por ejemplo `label`) — no se toca el audit trail. ```json { "label": "RP CDMX (renombrado)" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employer-registrations/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "label": "RP CDMX (renombrado)" }' ``` **Respuesta** ```json { "employer_registration_number": 5, "legal_entity_number": 1, "number": "Y6712345678", "label": "RP CDMX (renombrado)", "risk_class": 1, "risk_premium": "0.005", "premium_effective_from": "2026-01-01T00:00:00.000Z", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:15:32.000Z" } ``` ## Aplicar una nueva prima [#aplicar-una-nueva-prima] *** Para cambiar la clase de riesgo y/o la prima, envía los nuevos valores junto con `premium_effective_from`. La fila abierta del historial se cierra con `effective_to = "2026-02-01"` y se abre una nueva fila con `effective_from = "2026-02-01"`. `premium_effective_from` es **requerido** siempre que cambies `risk_class` o `risk_premium`. ```json { "risk_class": 2, "risk_premium": 0.0125, "premium_effective_from": "2026-02-01" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employer-registrations/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "risk_class": 2, "risk_premium": 0.0125, "premium_effective_from": "2026-02-01" }' ``` **Respuesta** ```json { "employer_registration_number": 5, "legal_entity_number": 1, "number": "Y6712345678", "label": "RP CDMX (renombrado)", "risk_class": 2, "risk_premium": "0.0125", "premium_effective_from": "2026-02-01T00:00:00.000Z", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:20:00.000Z" } ``` ## Marcar como inactivo [#marcar-como-inactivo] *** `is_active` lo controla el usuario y es **independiente del borrado**: un registro puede estar inactivo y seguir apareciendo en `GET /api/v1/employer-registrations`. **Desactivarlo (`is_active: false`) se rechaza con `409`** si algún empleo (cualquier estatus) referencia este registro patronal — reasígnalos primero. Para retirarlo del listado, usa [DELETE](/docs/developers/api-reference/employer-registrations/delete). ```json { "is_active": false } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employer-registrations/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "is_active": false }' ``` **Respuesta** ```json { "employer_registration_number": 5, "legal_entity_number": 1, "number": "Y6712345678", "label": "RP CDMX (renombrado)", "risk_class": 2, "risk_premium": "0.0125", "premium_effective_from": "2026-02-01T00:00:00.000Z", "is_active": false, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:22:44.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | ------------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `number` | string | Número IMSS. No se permite `null` (es columna requerida). Máx. 40 caracteres. | | `label` | string \| null | `null` o `""` limpian la etiqueta. | | `risk_class` | integer 1–5 | Cambiar este campo dispara una nueva fila en el audit trail; requiere `premium_effective_from` en el mismo cuerpo. | | `risk_premium` | number 0–1 | Hasta 7 decimales. Cambiar este campo dispara una nueva fila en el audit trail; requiere `premium_effective_from` en el mismo cuerpo. | | `premium_effective_from` | string `YYYY-MM-DD` | Fecha en que entra en vigor el nuevo par `(risk_class, risk_premium)`. Requerida cuando cambias cualquiera de los dos. | | `is_active` | boolean | Flag activo controlado por el usuario. Independiente del borrado. Ponerlo en `false` se rechaza con `409` si algún empleo referencia este registro patronal. | **No aceptado:** `legal_entity_number` — la razón social asignada al crear es inmutable. ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — misma forma que la respuesta de [crear](/docs/developers/api-reference/employer-registrations/create) en modo single. ## Errores [#errores] | Status | Causa | | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `employer_registration_number` inválido en la URL; falla de validación del cuerpo; fecha mal formada. | | `404` | El registro patronal con ese `employer_registration_number` no existe (o fue eliminado) en esta organización. | | `409` | Ya existe otro registro patronal con ese `number` para la misma razón social; o intento de desactivar (`is_active: false`) un registro referenciado por uno o más empleos (`{ "message": "This employer registration is still referenced by one or more employments. Reassign them before deactivating it." }`). | # Listar (/docs/developers/api-reference/employer-registrations/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de los registros patronales (IMSS) pertenecientes a tu organización. Los resultados se devuelven en formato JSON e incluyen metadatos de paginación. Cada registro está vinculado a una razón social mediante `legal_entity_number`. Los registros eliminados se excluyen automáticamente. El tamaño de página es fijo en 20 resultados. `risk_premium` se serializa como **string decimal** (por ejemplo `"0.0054355"`) para preservar la precisión. `premium_effective_from` se serializa como timestamp ISO 8601. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/employer-registrations` ## Consulta multiples registros patronales [#consulta-multiples-registros-patronales] *** ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | --------------------- | --------------------- | --------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `legal_entity_number` | integer | No | - | Restringe los resultados a una sola razón social. | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/employer-registrations" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 4, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "employer_registration_number": 1, "legal_entity_number": 1, "number": "Y6712345678", "label": "RP principal CDMX", "risk_class": 1, "risk_premium": "0.005", "premium_effective_from": "2026-01-01T00:00:00.000Z", "is_active": true, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ] } ``` ## Consulta un registro patronal especifico [#consulta-un-registro-patronal-especifico] *** Se puede buscar un registro patronal particular por su número org-scoped o por el número IMSS exacto. El campo `number` distingue mayúsculas y minúsculas. ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | ------------------------------ | ------- | --------- | ------- | ---------------------------------------------------------- | | `employer_registration_number` | integer | No | - | Filtra por número org-scoped exacto del registro patronal. | | `number` | string | No | - | Filtra por número IMSS exacto. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/employer-registrations?employer_registration_number=1" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "employer_registration_number": 1, "legal_entity_number": 1, "number": "Y6712345678", "label": "RP principal CDMX", "risk_class": 1, "risk_premium": "0.005", "premium_effective_from": "2026-01-01T00:00:00.000Z", "is_active": true, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | Valor inválido en `employer_registration_number`, `legal_entity_number` o `active`; o `legal_entity_number` no corresponde a una razón social de la organización. | | `401` | API key ausente o inválida. | # Editar en lote (/docs/developers/api-reference/employees/bulk-edit) ## Descripcion general [#descripcion-general] Este endpoint aplica **actualizaciones parciales a varios empleados** en una sola solicitud. El cuerpo es un **arreglo de 1 a 100 objetos**, y cada objeto identifica al empleado con `employee_number` y lleva los mismos campos que [editar un empleado](/docs/developers/api-reference/employees/edit). **Atómico (todo o nada).** Si **cualquier** fila falla la validación, **ningún** empleado se actualiza y la respuesta lista los problemas por fila para corregirlos en una sola pasada. Una solicitud exitosa devuelve `200` con un `summary` y el arreglo `updated` de empleados (objetos **planos**, mismo shape que [editar](/docs/developers/api-reference/employees/edit)). **Mismas reglas que la edición individual, por fila:** resolución de códigos/números (LE-scoped para registro patronal, calendario y contrato), colisiones de RFC/CURP, invariante IMSS, datos fijos por historial de nómina, créditos que impiden reclasificar, banco requerido para una cuenta sin CLABE, preparación para nómina (`activate` / degradación automática), historial de sueldos al cambiar el sueldo y reconciliación de unidades organizacionales. En el lote, esas reglas llegan como problemas de la fila en la respuesta `400`, no como `409`. La excepción es el alta en periodos cerrados: sin `confirm_retro: true` en la fila, la respuesta es `409` con `RETRO_CONFIRMATION_REQUIRED` y `retro_periods` en esa fila, y ningún empleado se actualiza. Para editar **un solo** empleado, usa [`PATCH /api/v1/employees/{employee_number}`](/docs/developers/api-reference/employees/edit) — este endpoint siempre espera un arreglo. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/employees` ## Actualizar varios empleados [#actualizar-varios-empleados] *** Envía un **arreglo** de hasta 100 objetos. Cada objeto necesita `employee_number` más los campos que quieras cambiar; el resto de cada empleado queda intacto. Si una fila falla, ninguna se aplica. ```json [ { "employee_number": 5, "department_code": "MKT", "job_position_code": null }, { "employee_number": 7, "daily_base_salary": 666.67 } ] ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employees" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "employee_number": 5, "department_code": "MKT", "job_position_code": null }, { "employee_number": 7, "daily_base_salary": 666.67 } ]' ``` **Respuesta** ```json { "summary": { "total": 2, "updated": 2 }, "updated": [ { "employee_number": 5, "status": "active", "department_code": "MKT", "job_position_code": null, "...": "resto de campos planos del empleado y su empleo" }, { "employee_number": 7, "status": "active", "daily_base_salary": "666.6700", "...": "resto de campos planos del empleado y su empleo" } ] } ``` ## Cuerpo de la solicitud [#cuerpo-de-la-solicitud] El cuerpo es un **arreglo** de 1 a 100 objetos. Cada objeto: | Campo | Tipo | Notas | | ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `employee_number` | integer positivo | **Requerido.** Identifica al empleado a actualizar dentro de la organización. | | *(resto de campos)* | — | Idénticos a [`PATCH /api/v1/employees/{employee_number}`](/docs/developers/api-reference/employees/edit): datos personales, laborales, compensación, `organizational_units`, `activate`, `confirm_retro` y valores de campos personalizados inline. Omitir una clave la deja intacta; `null` la limpia. | ## Forma de las respuestas [#forma-de-las-respuestas] ### `200` — actualización exitosa [#200--actualización-exitosa] La respuesta envuelve los resultados en `summary` + `updated[]`. Cada elemento de `updated` es el objeto **plano** del empleado (datos personales + los de su empleo actual), igual que la respuesta de [editar](/docs/developers/api-reference/employees/edit). ```json { "summary": { "total": 2, "updated": 2 }, "updated": [ { "employee_number": 5, "...": "campos planos como en editar" }, { "employee_number": 7, "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida [#400--validación-fallida] Reporte por fila. **Ningún** empleado del lote fue actualizado. ```json { "message": "Validation failed for 1 employee(s). No employees were updated.", "summary": { "total": 2, "with_errors": 1 }, "errors": [ { "index": 1, "employee_number": 99, "issues": [ { "field": "employee_number", "code": "EMPLOYEE_NOT_FOUND", "message": "No employee with number '99' in this organization." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `employee_number` — el número de empleado de esa fila, para identificarla sin contar. * `issues[]` — todos los problemas de esa fila juntos. También devuelve `400` (sin `errors[]` por fila) cuando el cuerpo no es un arreglo, está vacío o excede 100 elementos. ### `409` — condición de carrera [#409--condición-de-carrera] Otra solicitud concurrente reclamó un RFC o CURP entre la validación y la escritura. La transacción completa hace rollback — ningún empleado es actualizado. Reintenta. ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] | Codigo | Significado | | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `EMPLOYEE_NOT_FOUND` | El `employee_number` no existe (o fue eliminado) en la organización. | | `DUPLICATE_EMPLOYEE_NUMBER_IN_BATCH` | El mismo `employee_number` aparece en más de una fila del lote. | | `NO_OPEN_EMPLOYMENT` | Se enviaron campos laborales para un empleado dado de baja. Reactívalo primero. | | `BELOW_MINIMUM_WAGE` | El `daily_base_salary` efectivo es menor al salario mínimo diario vigente de la `minimum_wage_zone` efectiva. El mínimo se toma del catálogo `salario_minimo` de la organización y respeta el override de la razón social. | | `ACCOUNT_NUMBER_TOO_LONG` | El `account_number` efectivo excede los dígitos permitidos para el `bank` efectivo: 12 para BANREGIO, 11 para cualquier otro banco. | | `BANK_REQUIRED_FOR_ACCOUNT` | El `account_number` efectivo no tiene `bank` ni una `clabe` válida; el recibo necesita nombrar el banco. Solo se revisa si la fila envía `bank`, `clabe` o `account_number`. | | `LOCKED_BY_PAYROLL_HISTORY` | La fila cambia `worker_type`, `contributes_to_imss` o `employer_registration` de un empleado con una nómina cerrada en la que se le pagó o con su alta ante el IMSS ya presentada. Esos datos cambian con una baja y un reingreso. | | `LOCKED_BY_OPEN_PAYROLL` | La fila cambia `payroll_calendar_number` mientras el empleado está en una nómina abierta. Ciérrala o cancélala primero. | | `WORKER_TYPE_HAS_CREDITS` | La fila cambia a un `subordinate` con créditos INFONAVIT o FONACOT activos o suspendidos a `asimilado` u `honorarios`. | | `DUPLICATE_RFC` / `DUPLICATE_CURP` | El nuevo RFC/CURP colisiona con otro empleado de la organización. | | `DUPLICATE_RFC_IN_BATCH` / `DUPLICATE_CURP_IN_BATCH` | El mismo RFC/CURP aparece en más de una fila del lote. | | `JOB_POSITION_NOT_FOUND` / `BRANCH_NOT_FOUND` / `DEPARTMENT_NOT_FOUND` / `COST_CENTER_NOT_FOUND` | El código no coincide con un recurso activo. | | `EMPLOYER_REGISTRATION_NOT_FOUND` / `PAYROLL_CALENDAR_NOT_FOUND` / `CONTRACT_NOT_FOUND` | El número no resuelve dentro de la razón social del empleo. | | `ORGANIZATIONAL_UNIT_NOT_FOUND` / `ORGANIZATIONAL_UNIT_DUPLICATE_TYPE` | Unidad inexistente, o dos unidades del mismo tipo en `organizational_units`. | | `IMSS_REGISTRATION_REQUIRED` | `contributes_to_imss` efectivo es `true` pero falta `employer_registration`. | | `WORKER_TYPE_IMSS_CONFLICT` | `worker_type` efectivo no es `subordinate` pero `contributes_to_imss` efectivo es `true`. | | `WORKER_REGIME_CONFLICT` | El `regimen_fiscal` efectivo no corresponde al `worker_type` efectivo: `02` o `13` para `subordinate`, del `05` al `11` para `asimilado` y `honorarios`. | | `WORKER_TYPE_FIELD_CONFLICT` | El `worker_type` efectivo no es `subordinate` pero el `imss_number` o el `employer_registration` efectivo (el que indica `field`) no está vacío. Envíalo en `null` en la misma fila. La fecha de antigüedad no se valida: reclasificar a `asimilado` u `honorarios` la borra. | | `ANNUAL_ISR_WORKER_TYPE_CONFLICT` | `receives_annual_isr_adjustment` es `true` pero el `worker_type` efectivo no es `subordinate`. Reemplaza a `ANNUAL_ISR_OPT_IN_CONFLICT`. | | `ACTIVATION_INCOMPLETE` | `activate: true` en un empleado que no está listo para nómina. | | `RETRO_CONFIRMATION_REQUIRED` | `activate: true` en un empleado cuya fecha de ingreso cae en periodos cerrados, sin `confirm_retro: true` (respuesta `409`). | | `CUSTOM_FIELD_NOT_FOUND` / `CUSTOM_FIELD_INVALID_TYPE` | Campo personalizado inexistente o de tipo incorrecto. | | `INVALID_INPUT` | Falla genérica de validación de esquema (formato, tipo). | | `CONCURRENT_MODIFICATION` | Señal de reintento por condición de carrera (respuesta `409`). | # Crear (/docs/developers/api-reference/employees/create) ## Descripcion general [#descripcion-general] Este endpoint permite crear uno o varios empleados dentro de tu organización. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. Cada empleado se crea vinculado a la `legal_entity_number` enviada. Una solicitud exitosa devuelve `201` con los empleados creados como un objeto **plano** que incluye los datos personales, los de su empleo (estatus, fechas, asignaciones y compensación) y los campos generados por el servidor (`employee_number`, `created_at`, `updated_at`). **Estatus y alta de nómina.** Si omites `activate`, el empleado solo se crea con estatus `active` cuando está **listo para nómina**, es decir, cuando la fila incluye `legal_entity_number`, `rfc`, `curp`, `postal_code` (o `home_postal_code` si `fiscal_address_same_as_home` es `true`), `work_center_state_code`, `daily_base_salary`, `regimen_fiscal`, `work_shift_type`, `payroll_calendar_number`, `contract_number`, y además `imss_number`, `imss_worker_type` e `imss_work_schedule` si `contributes_to_imss` es `true`. Si falta alguno, se crea como `pending` (Sin alta en nómina). Con `activate: true`, un empleado que no está listo se rechaza con `400` `ACTIVATION_INCOMPLETE` y la lista de datos faltantes; envía `activate: false` para forzar `pending`. Si el alta cae en periodos de nómina ya **cerrados**, la llamada se rechaza con `409` `RETRO_CONFIRMATION_REQUIRED` (con `retro_periods`); reenvíala con `confirm_retro: true` y cada periodo se paga con un ajuste retroactivo en la siguiente nómina. Cuando capturas `daily_base_salary`, se registra automáticamente la primera fila del historial de sueldos. En modo lote, si **cualquier** fila falla la validación, **ningún** empleado se crea — la respuesta lista los problemas por fila para corregirlos en una sola pasada. Si envías `termination_date` en una fila, el empleado se crea **dado de baja**, útil al cargar el historial de alguien que ya se fue. La fecha no puede ser posterior a hoy ni anterior a `hire_date`, y la fila no puede traer `activate: true` (sino, `400`). Un empleado creado dado de baja nunca pide `confirm_retro` ni genera ajustes retroactivos. Si `contributes_to_imss` es `true`, `employer_registration` se vuelve obligatorio en la misma fila (sino, `400`). **Movimientos IMSS.** Cada empleado creado que aporta al IMSS genera su alta en Movimientos IMSS, y su baja si la fila trae `termination_date`. Envía `already_registered_with_imss: true` cuando el alta ya se presentó ante el IMSS fuera de la plataforma, por ejemplo al migrar desde otro sistema: queda registrada como ya presentada, sin fecha límite y sin SBC calculado. Por defecto es `false` y queda pendiente de presentar. La baja sigue `termination_already_registered_with_imss`; si lo omites, toma la misma respuesta que el alta. **Tipo de trabajador.** `worker_type` dice qué es la persona para efectos estatutarios: `subordinate` (por defecto), `asimilado` u `honorarios`. No se infiere del tipo de contrato SAT. Un asimilado o un trabajador por honorarios recibe sueldo e ISR sin subsidio, sin IMSS, sin SBC ni prestaciones de ley, así que `contributes_to_imss` debe ser `false` para ellos (sino, `400`). Por la misma razón no llevan `imss_number`, `employer_registration` ni `seniority_date`: enviar cualquiera de ellos con un `worker_type` distinto de `subordinate` responde `400` en ese campo. **Horario de trabajo.** El horario se asigna al empleado, no a su contrato: dos personas con el mismo contrato pueden tener horarios distintos. Envía `work_schedule_number` para un horario semanal fijo **o** `work_schedule_bundle_number` para una rotación — nunca ambos (`400`). Con una rotación puedes indicar `work_schedule_bundle_start_step` para escalonar al empleado dentro del ciclo (una cuadrilla en matutino mientras otra va en nocturno); si lo omites, arranca en el primer paso de la rotación, que es lo que lo mantiene sincronizado con ella. Un `work_schedule_bundle_start_step` sin rotación en el mismo cuerpo se rechaza con `400`. Si no envías ninguno de los dos, el empleado sigue el horario predeterminado de la organización. > **Nota sobre los números de horario:** los horarios y las rotaciones aún no se exponen como endpoint propio en la API; utiliza el número visible en **Configuración → Tiempo y Acceso → Horarios**. **Campos personalizados.** Los valores de campos personalizados se envían como **claves planas en la raíz del cuerpo del empleado**, junto a `first_name`, `rfc`, etc. La clave es el `key` de la definición (gestionada en [Campos personalizados → Empleados](/docs/developers/api-reference/custom-fields/employees/list)) y el valor debe ser compatible con el `type` de la definición. Cualquier definición activa marcada con `is_required_for_create: true` debe enviarse en **cada** fila — si falta, la fila falla con `CUSTOM_FIELD_REQUIRED`. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/employees` ## Solicitud unica — minima [#solicitud-unica--minima] *** Envía solo los seis campos obligatorios. Al no incluir la compensación completa, el empleado se crea como `pending` (Sin alta en nómina), vinculado a la razón social indicada, sin puesto, sucursal, departamento ni centro de costos. `contributes_to_imss` toma `false` por defecto y `is_included_in_payroll_calculations` toma `true`. ```json { "first_name": "María", "paternal_last_name": "González", "rfc": "GOPM900101AB1", "curp": "GOPM900101MDFNRR07", "legal_entity_number": 1, "hire_date": "2026-01-15", "work_center_state_code": "CMX" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employees" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "first_name": "María", "paternal_last_name": "González", "rfc": "GOPM900101AB1", "curp": "GOPM900101MDFNRR07", "legal_entity_number": 1, "hire_date": "2026-01-15", "work_center_state_code": "CMX" }' ``` **Respuesta** ```json { "employee_number": 5, "internal_number": null, "first_name": "María", "middle_name": null, "paternal_last_name": "González", "maternal_last_name": null, "gender": null, "birth_date": null, "birth_country": null, "birth_state": null, "birth_state_code": null, "birth_municipality_code": null, "birth_city": null, "birth_place": null, "nationality": null, "phone_number": null, "secondary_phone_number": null, "rfc": "GOPM900101AB1", "curp": "GOPM900101MDFNRR07", "postal_code": null, "work_center_state_code": "CMX", "home_street": null, "home_exterior_number": null, "home_interior_number": null, "home_neighborhood": null, "home_municipality_code": null, "home_city": null, "home_state_code": null, "home_postal_code": null, "fiscal_address_same_as_home": false, "domicile_state_code": null, "fiscal_street": null, "fiscal_exterior_number": null, "fiscal_interior_number": null, "fiscal_neighborhood": null, "fiscal_municipality_code": null, "fiscal_city": null, "bank": null, "clabe": null, "account_number": null, "card_number": null, "notes": null, "legal_entity_number": 1, "status": "pending", "hire_date": "2026-01-15T00:00:00.000Z", "seniority_date": null, "termination_date": null, "job_position_code": null, "branch_code": null, "department_code": null, "cost_center_code": null, "employer_registration": null, "imss_number": null, "contributes_to_imss": false, "worker_type": "subordinate", "minimum_wage_zone": "general", "imss_worker_type": null, "imss_work_schedule": null, "salary_type": null, "is_unionized": false, "receives_annual_isr_adjustment": true, "opts_into_annual_isr_adjustment": false, "is_included_in_payroll_calculations": true, "daily_base_salary": null, "regimen_fiscal": null, "work_shift_type": null, "payroll_calendar_number": null, "contract_number": null, "work_schedule_number": null, "work_schedule_bundle_number": null, "work_schedule_bundle_start_step": null, "organizational_units": [], "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ## Solicitud unica — con datos laborales + campos personalizados [#solicitud-unica--con-datos-laborales--campos-personalizados] *** Para asignar puesto, departamento, sucursal, centro de costos o registro patronal al empleado, envía los códigos correspondientes. Al incluir además la compensación completa (`postal_code` —o `home_postal_code` con `fiscal_address_same_as_home: true`—, `daily_base_salary`, `regimen_fiscal`, `work_shift_type`, `payroll_calendar_number`, `contract_number`), el empleado queda **listo para nómina** y se crea con estatus `active`. Cuando `contributes_to_imss` es `true`, `employer_registration` es obligatorio. Los valores de campos personalizados van como **claves planas en la raíz del cuerpo**, utilizando el `key` de la definición. ```json { "first_name": "María", "paternal_last_name": "González", "maternal_last_name": "Pérez", "rfc": "GOPM900101AB1", "curp": "GOPM900101MDFNRR07", "legal_entity_number": 1, "hire_date": "2026-01-15", "work_center_state_code": "CMX", "postal_code": "06000", "department_code": "VEN", "job_position_code": "GER-VEN", "branch_code": "CDMX", "cost_center_code": "CC01", "contributes_to_imss": true, "already_registered_with_imss": false, "employer_registration": "Y6712345678", "imss_number": "12345678903", "daily_base_salary": 500, "regimen_fiscal": "02", "work_shift_type": "01", "payroll_calendar_number": 2, "contract_number": 5, "work_schedule_number": 3, "work_schedule_bundle_number": null, "work_schedule_bundle_start_step": null, "organizational_units": [10], "shirt_size": "M", "fit_for_duty": true, "years_experience": 7 } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employees" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "first_name": "María", "paternal_last_name": "González", "maternal_last_name": "Pérez", "rfc": "GOPM900101AB1", "curp": "GOPM900101MDFNRR07", "legal_entity_number": 1, "hire_date": "2026-01-15", "work_center_state_code": "CMX", "postal_code": "06000", "department_code": "VEN", "job_position_code": "GER-VEN", "branch_code": "CDMX", "cost_center_code": "CC01", "contributes_to_imss": true, "already_registered_with_imss": false, "employer_registration": "Y6712345678", "imss_number": "12345678903", "daily_base_salary": 500, "regimen_fiscal": "02", "work_shift_type": "01", "payroll_calendar_number": 2, "contract_number": 5, "work_schedule_number": 3, "work_schedule_bundle_number": null, "work_schedule_bundle_start_step": null, "organizational_units": [10], "shirt_size": "M", "fit_for_duty": true, "years_experience": 7 }' ``` **Respuesta** ```json { "employee_number": 5, "internal_number": null, "first_name": "María", "middle_name": null, "paternal_last_name": "González", "maternal_last_name": "Pérez", "gender": null, "birth_date": null, "birth_country": null, "birth_state": null, "birth_state_code": null, "birth_municipality_code": null, "birth_city": null, "birth_place": null, "nationality": null, "phone_number": null, "secondary_phone_number": null, "rfc": "GOPM900101AB1", "curp": "GOPM900101MDFNRR07", "postal_code": "06000", "work_center_state_code": "CMX", "home_street": null, "home_exterior_number": null, "home_interior_number": null, "home_neighborhood": null, "home_municipality_code": null, "home_city": null, "home_state_code": null, "home_postal_code": null, "fiscal_address_same_as_home": false, "domicile_state_code": null, "fiscal_street": null, "fiscal_exterior_number": null, "fiscal_interior_number": null, "fiscal_neighborhood": null, "fiscal_municipality_code": null, "fiscal_city": null, "bank": null, "clabe": null, "account_number": null, "card_number": null, "notes": null, "legal_entity_number": 1, "status": "active", "hire_date": "2026-01-15T00:00:00.000Z", "seniority_date": null, "termination_date": null, "job_position_code": "GER-VEN", "branch_code": "CDMX", "department_code": "VEN", "cost_center_code": "CC01", "employer_registration": "Y6712345678", "imss_number": "12345678903", "contributes_to_imss": true, "worker_type": "subordinate", "minimum_wage_zone": "general", "imss_worker_type": "permanent", "imss_work_schedule": "full", "salary_type": null, "is_unionized": false, "receives_annual_isr_adjustment": true, "opts_into_annual_isr_adjustment": false, "is_included_in_payroll_calculations": true, "daily_base_salary": "500.0000", "regimen_fiscal": "02", "work_shift_type": "01", "payroll_calendar_number": 2, "contract_number": 5, "work_schedule_number": 3, "work_schedule_bundle_number": null, "work_schedule_bundle_start_step": null, "organizational_units": [ { "organizational_unit_number": 10, "organizational_unit_type_key": "region" } ], "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ## Solicitud por lote (1–100 empleados) [#solicitud-por-lote-1100-empleados] *** Envía un **arreglo** de hasta 100 empleados en el mismo endpoint. Si **cualquier** fila falla la validación, **ningún** empleado se crea y la respuesta lista los problemas por fila. ```json [ { "first_name": "María", "paternal_last_name": "González", "rfc": "GOPM900101AB1", "curp": "GOPM900101MDFNRR07", "legal_entity_number": 1, "hire_date": "2026-01-15", "work_center_state_code": "CMX" }, { "first_name": "Juan", "paternal_last_name": "Pérez", "rfc": "PEXJ850715XY2", "curp": "PEXJ850715HDFRRN09", "legal_entity_number": 1, "hire_date": "2026-01-15", "work_center_state_code": "CMX" } ] ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employees" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "first_name": "María", "paternal_last_name": "González", "rfc": "GOPM900101AB1", "curp": "GOPM900101MDFNRR07", "legal_entity_number": 1, "hire_date": "2026-01-15", "work_center_state_code": "CMX" }, { "first_name": "Juan", "paternal_last_name": "Pérez", "rfc": "PEXJ850715XY2", "curp": "PEXJ850715HDFRRN09", "legal_entity_number": 1, "hire_date": "2026-01-15", "work_center_state_code": "CMX" } ]' ``` **Respuesta** ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "employee_number": 6, "first_name": "María", "paternal_last_name": "González", "status": "pending", "legal_entity_number": 1, "...": "resto de campos planos como en la respuesta única" }, { "employee_number": 7, "first_name": "Juan", "paternal_last_name": "Pérez", "status": "pending", "legal_entity_number": 1, "...": "..." } ] } ``` ## Campos obligatorios [#campos-obligatorios] | Campo | Tipo | Notas | | ------------------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `first_name` | string | Mínimo 1 carácter tras recortar. | | `paternal_last_name` | string | Mínimo 1 carácter tras recortar. | | `rfc` | string | 12 o 13 caracteres. Se almacena en mayúsculas. Único por organización. | | `curp` | string | Exactamente 18 caracteres. Se almacena en mayúsculas. Único por organización. | | `legal_entity_number` | integer positivo | Debe corresponder a una razón social existente en la organización. Define la razón social del empleado. | | `hire_date` | string `YYYY-MM-DD` | Fecha de alta del empleado. | | `work_center_state_code` | string | Entidad del centro de trabajo, clave SAT c\_Estado de 3 letras (por ejemplo `CMX`). Define la tasa del impuesto sobre nómina. | ## Campos opcionales — datos personales del empleado [#campos-opcionales--datos-personales-del-empleado] | Campo | Tipo | Notas | | ----------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `middle_name` | string | | | `maternal_last_name` | string | | | `gender` | `male` \| `female` \| `unspecified` | | | `birth_date` | string `YYYY-MM-DD` | | | `birth_country` | string | | | `birth_state` | string | Nombre del estado de nacimiento. Se acepta por compatibilidad: si coincide con un estado del catálogo se guarda su clave; si no, se conserva el texto. Prefiere `birth_state_code`. | | `birth_city` | string | Nombre del municipio de nacimiento. Se acepta por compatibilidad: si coincide con un municipio del estado de nacimiento se guarda su clave; si no, se conserva el texto. Prefiere `birth_municipality_code`. | | `birth_state_code` | string | Estado de nacimiento, clave SAT c\_Estado de 3 letras (por ejemplo `JAL`). | | `birth_municipality_code` | string | Municipio de nacimiento, clave INEGI de 3 dígitos dentro de `birth_state_code` (por ejemplo `120`). | | `birth_place` | string | | | `nationality` | string | Nacionalidad en gentilicio, del catálogo de nacionalidades (por ejemplo `Mexicana`). No distingue mayúsculas, acentos ni género, y acepta el nombre del país, la forma en inglés o la clave de tres letras: `MEXICANO`, `México` y `MEX` se guardan como `Mexicana`. Un valor fuera del catálogo responde `INVALID_NATIONALITY`. | | `phone_number` | string | Teléfono del empleado con clave de país y sin espacios, por ejemplo `+529611234567`. | | `secondary_phone_number` | string | Teléfono secundario. Mismo formato que `phone_number`. | | `postal_code` | string `#####` | Código postal de 5 dígitos del domicilio fiscal (el de la Constancia de Situación Fiscal), que es el que valida el CFDI. Cuenta para la preparación de nómina. Se ignora si `fiscal_address_same_as_home` es `true`. | | `home_street` | string | Calle del domicilio real (donde vive el empleado). Independiente del domicilio fiscal. | | `home_exterior_number` | string | Número exterior del domicilio real. | | `home_interior_number` | string | Número interior del domicilio real. | | `home_neighborhood` | string | Colonia del domicilio real. | | `home_municipality_code` | string | Municipio o alcaldía del domicilio real, clave INEGI de 3 dígitos dentro de `home_state_code` (por ejemplo `039`). La respuesta devuelve además `home_city` con el nombre. | | `home_city` | string | Nombre del municipio o alcaldía. Se acepta por compatibilidad: si coincide con un municipio de `home_state_code` se guarda su clave; si no, se conserva el texto. Prefiere `home_municipality_code`. | | `home_state_code` | string | Estado del domicilio real, clave SAT c\_Estado de 3 letras (por ejemplo `NLE`). | | `home_postal_code` | string `#####` | Código postal de 5 dígitos del domicilio real. Solo cuenta para la preparación de nómina si `fiscal_address_same_as_home` es `true`. | | `fiscal_address_same_as_home` | boolean | `true` si el domicilio fiscal es el mismo que el real: se copia del domicilio real (`home_*`) y se ignoran `postal_code`, `domicile_state_code` y los `fiscal_*` enviados. Si se omite, es `false`. | | `domicile_state_code` | string | Estado del domicilio fiscal, clave SAT c\_Estado de 3 letras (por ejemplo `NLE`). | | `fiscal_street` | string | Calle del domicilio fiscal. | | `fiscal_exterior_number` | string | Número exterior del domicilio fiscal. | | `fiscal_interior_number` | string | Número interior del domicilio fiscal. | | `fiscal_neighborhood` | string | Colonia del domicilio fiscal. | | `fiscal_municipality_code` | string | Municipio o alcaldía del domicilio fiscal, clave INEGI de 3 dígitos dentro de `domicile_state_code`. La respuesta devuelve además `fiscal_city` con el nombre. | | `fiscal_city` | string | Nombre del municipio o alcaldía del domicilio fiscal. Se acepta por compatibilidad: si coincide con un municipio de `domicile_state_code` se guarda su clave; si no, se conserva el texto. Prefiere `fiscal_municipality_code`. | | `internal_number` | string | Número interno que la organización asigna por su cuenta (por ejemplo, el de una nómina anterior). Texto libre, único en la organización sin distinguir mayúsculas: si otro empleado ya lo tiene, la respuesta es `409` con `DUPLICATE_INTERNAL_NUMBER`. | | `bank` | string | Nombre del banco, máximo 60 caracteres. Texto libre; utiliza el nombre del catálogo SAT c\_Banco para que coincida con el selector de la plataforma. | | `clabe` | string `18 dígitos` | CLABE interbancaria. Se valida el dígito verificador. | | `account_number` | string | Número de cuenta, solo dígitos: máximo 12 si `bank` es BANREGIO, máximo 11 para cualquier otro banco. Sin una `clabe` válida, requiere `bank` (sino `400` en `bank`), porque el recibo nombra el banco. | | `card_number` | string `16 dígitos` | Número de tarjeta. | | `notes` | string | Notas libres. | ## Campos opcionales — datos laborales del empleado [#campos-opcionales--datos-laborales-del-empleado] | Campo | Tipo | Notas | | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `job_position_code` | string | Código de un puesto activo en la organización. | | `branch_code` | string | Código de una sucursal activa. | | `department_code` | string | Código de un departamento activo. | | `cost_center_code` | string | Código de un centro de costos activo. | | `employer_registration` | string | Número de registro patronal IMSS, dentro de la `legal_entity_number` de la fila. Solo para `subordinate` (sino `400`). | | `seniority_date` | string `YYYY-MM-DD` | Si se omite, el empleado no lleva fecha de antigüedad. Solo para `subordinate` (sino `400`). No puede ser posterior a hoy en la hora de la Ciudad de México (sino `400`). | | `termination_date` | string `YYYY-MM-DD` | Si se envía, el empleado se crea como dado de baja (uso histórico). No puede ser posterior a hoy ni anterior a `hire_date`, ni enviarse con `activate: true`. | | `imss_number` | string | NSS de 11 dígitos. Se valida el dígito verificador. Solo para `subordinate` (sino `400`). | | `contributes_to_imss` | boolean | Por defecto `false`. Cuando es `true`, `employer_registration` es obligatorio (sino `400`). Debe ser `false` cuando `worker_type` no es `subordinate`. | | `already_registered_with_imss` | boolean | Por defecto `false`. Con `true`, el alta en Movimientos IMSS (y la baja, si la fila trae `termination_date` sin `termination_already_registered_with_imss`) se registra como ya presentada ante el IMSS fuera de la plataforma: no queda pendiente ni se calcula su SBC. Con `false`, el movimiento queda pendiente de presentar. Solo tiene efecto cuando `contributes_to_imss` es `true`. | | `termination_already_registered_with_imss` | boolean | Solo con `termination_date`. Con `true`, la baja se registra como ya presentada ante el IMSS; con `false`, queda pendiente de presentar. Si se omite, toma el valor de `already_registered_with_imss`. | | `worker_type` | `subordinate` \| `asimilado` \| `honorarios` | Por defecto `subordinate`. Un asimilado o un trabajador por honorarios recibe sueldo e ISR sin subsidio, sin IMSS, sin SBC ni prestaciones de ley. | | `minimum_wage_zone` | `general` \| `northern_border` | Por defecto `general` (Zona del Salario Mínimo General). `northern_border` es la Zona Libre de la Frontera Norte. Determina el salario mínimo contra el que se valida `daily_base_salary` y el que utiliza el cálculo de nómina. | | `salary_type` | `fixed` \| `mixed` \| `variable` \| null | Opcional. Tipo de salario del empleado: fijo, mixto o variable. Es informativo: no cambia el cálculo de la nómina. | | `imss_worker_type` | `permanent` \| `eventual_city` \| `eventual_construction` \| `eventual_rural` \| null | Opcional. Tipo de trabajador IMSS: permanente, eventual de la ciudad, eventual de la construcción o eventual del campo. Obligatorio para dar de alta en nómina si `contributes_to_imss` es `true`; si es `false`, se guarda vacío. | | `imss_work_schedule` | `full` \| `one_day_week` \| `two_day_week` \| `three_day_week` \| `four_day_week` \| `five_day_week` \| `reduced_day` \| null | Opcional. Semana o jornada reducida: semana completa, semana reducida de 1 a 5 días o jornada reducida. Obligatorio para dar de alta en nómina si `contributes_to_imss` es `true`; si es `false`, se guarda vacío. | | `is_unionized` | boolean | Por defecto `false`. Indica si el trabajador está sindicalizado. | | `receives_annual_isr_adjustment` | boolean | Por defecto `true` para un `subordinate` y `false` para los demás. Un subordinado entra al ajuste anual de ISR solo si es `true`. Debe ser `false` cuando `worker_type` no es `subordinate` (sino `400`): un asimilado o un trabajador por honorarios nunca entra al ajuste. | | `opts_into_annual_isr_adjustment` | boolean | **Obsoleto:** utiliza `receives_annual_isr_adjustment`. Un `true` se acepta como su equivalente; un `false` se ignora, porque antes no excluía a ningún subordinado. Si envías ambos, gana `receives_annual_isr_adjustment`. Las respuestas lo devuelven siempre en `false`: su significado era la solicitud de un asimilado o un trabajador por honorarios, que ya no existe. Antes un subordinado siempre entraba al ajuste; ahora `receives_annual_isr_adjustment` decide. Un error sobre este campo se reporta con el nombre que enviaste. | | `is_included_in_payroll_calculations` | boolean | Por defecto `true`. | | `daily_base_salary` | number | Sueldo diario base (> 0). Registra la primera fila del historial de sueldos. Cuenta para la preparación de nómina. | | `regimen_fiscal` | string | Clave SAT del régimen del trabajador (c\_TipoRegimen). Cuenta para la preparación de nómina. Debe corresponder al `worker_type`: `02` o `13` para `subordinate`, del `05` al `11` para `asimilado` y `honorarios` (sino `400`). | | `work_shift_type` | string | Clave SAT del tipo de jornada (c\_TipoJornada), por ejemplo `01` diurna. Va en el recibo timbrado y cuenta para la preparación de nómina. | | `benefit_scheme_number` | null | Ya no existe: las prestaciones del empleado vienen de su contrato (`contract_number`). Se acepta `null` y se ignora; cualquier otro valor responde `400`. | | `payroll_calendar_number` | integer positivo | Número del calendario de nómina; debe pertenecer a la `legal_entity_number` de la fila. | | `contract_number` | integer positivo | Número del contrato; debe estar vinculado a la `legal_entity_number` de la fila. | | `work_schedule_number` | integer positivo | Horario semanal fijo del empleado. Debe ser un horario **activo** de la organización. Excluyente con `work_schedule_bundle_number`. | | `work_schedule_bundle_number` | integer positivo | Rotación de horarios del empleado. Debe ser una rotación **activa** de la organización. Excluyente con `work_schedule_number`. | | `work_schedule_bundle_start_step` | integer positivo | Paso del ciclo en el que arranca el empleado. **Opcional**: si lo omites, se utiliza el primer paso de la rotación. Requiere `work_schedule_bundle_number` en el mismo cuerpo. | | `organizational_units` | integer\[] | Arreglo de números de unidad organizacional. Máximo una unidad por tipo. | | `activate` | boolean | Omitido: da de alta (`active`) solo si el empleado está listo para nómina; de lo contrario queda `pending`. `true` exige que esté listo (sino `400` `ACTIVATION_INCOMPLETE`). `false` fuerza `pending`. | | `confirm_retro` | boolean | Autoriza que un alta con fecha de ingreso dentro de periodos **cerrados** se pague con ajustes retroactivos. Sin él, esa alta responde `409` `RETRO_CONFIRMATION_REQUIRED` y no se crea ningún empleado. | ## Campos opcionales — valores de campos personalizados [#campos-opcionales--valores-de-campos-personalizados] Los valores de campos personalizados se envían como **claves planas en la raíz del cuerpo del empleado**, no dentro de un objeto envoltorio. La clave es el `key` de la definición y el valor debe ser compatible con el `type` declarado: | `type` de la definición | Valor JSON aceptado | | ----------------------- | ------------------------------- | | `text`, `select` | String no vacío. | | `number` | Número finito. | | `boolean` | `true` o `false`. | | `date` | String en formato `YYYY-MM-DD`. | Si una definición activa tiene `is_required_for_create: true`, su `key` debe enviarse en cada fila. Las definiciones opcionales pueden omitirse. ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — solicitud única [#201--solicitud-única] Cuando el cuerpo es **un objeto**, la respuesta es el objeto **plano** del empleado creado: datos personales + los de su empleo actual. El `status` es `active` solo si el empleado quedó listo para nómina; de lo contrario, `pending`. ```json { "employee_number": 5, "internal_number": null, "first_name": "María", "middle_name": null, "paternal_last_name": "González", "maternal_last_name": null, "gender": null, "birth_date": null, "birth_country": null, "birth_state": null, "birth_state_code": null, "birth_municipality_code": null, "birth_city": null, "birth_place": null, "nationality": null, "phone_number": null, "secondary_phone_number": null, "rfc": "GOPM900101AB1", "curp": "GOPM900101MDFNRR07", "postal_code": null, "work_center_state_code": "CMX", "home_street": null, "home_exterior_number": null, "home_interior_number": null, "home_neighborhood": null, "home_municipality_code": null, "home_city": null, "home_state_code": null, "home_postal_code": null, "fiscal_address_same_as_home": false, "domicile_state_code": null, "fiscal_street": null, "fiscal_exterior_number": null, "fiscal_interior_number": null, "fiscal_neighborhood": null, "fiscal_municipality_code": null, "fiscal_city": null, "bank": null, "clabe": null, "account_number": null, "card_number": null, "notes": null, "legal_entity_number": 1, "status": "pending", "hire_date": "2026-01-15T00:00:00.000Z", "seniority_date": null, "termination_date": null, "job_position_code": null, "branch_code": null, "department_code": null, "cost_center_code": null, "employer_registration": null, "imss_number": null, "contributes_to_imss": false, "worker_type": "subordinate", "minimum_wage_zone": "general", "imss_worker_type": null, "imss_work_schedule": null, "salary_type": null, "is_unionized": false, "receives_annual_isr_adjustment": true, "opts_into_annual_isr_adjustment": false, "is_included_in_payroll_calculations": true, "daily_base_salary": null, "regimen_fiscal": null, "work_shift_type": null, "payroll_calendar_number": null, "contract_number": null, "work_schedule_number": null, "work_schedule_bundle_number": null, "work_schedule_bundle_start_step": null, "organizational_units": [], "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ### `201` — solicitud por lote [#201--solicitud-por-lote] Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "employee_number": 6, "...": "..." }, { "employee_number": 7, "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única] Lista plana de problemas para el único empleado: ```json { "message": "Validation failed.", "errors": [ { "field": "rfc", "code": "DUPLICATE_RFC", "message": "An employee with this 'rfc' already exists in this organization." } ] } ``` Para fallas de validación de esquema (campo faltante, tipo incorrecto), `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "rfc": ["Ingresa el RFC."], "hire_date": ["Ingresa la fecha de ingreso."] } } ``` ### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote] Reporte por fila. **Ningún** empleado del lote fue creado. ```json { "message": "Validation failed for 1 employee(s). No employees were created.", "summary": { "total": 2, "with_errors": 1 }, "errors": [ { "index": 0, "employee": { "first_name": "María", "paternal_last_name": "González" }, "issues": [ { "field": "legal_entity_number", "code": "LEGAL_ENTITY_NOT_FOUND", "message": "No legal entity with number '99' in this organization." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `employee.first_name` / `employee.paternal_last_name` — extraídos de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. ### `409`: alta en periodos cerrados, número interno duplicado o condición de carrera [#409-alta-en-periodos-cerrados-número-interno-duplicado-o-condición-de-carrera] Si un empleado se da de alta con una fecha de ingreso dentro de periodos ya cerrados y no enviaste `confirm_retro: true`, la respuesta es `409` con `errors[].code` `RETRO_CONFIRMATION_REQUIRED` y `retro_periods`. En modo lote, el error llega en la fila correspondiente y ningún empleado es creado. Si otro empleado de la organización ya tiene el `internal_number` que enviaste (sin distinguir mayúsculas), la respuesta es `409` con `errors[].code` `DUPLICATE_INTERNAL_NUMBER`. En modo lote, dos filas con el mismo número también lo producen, y ningún empleado es creado. Otra solicitud concurrente reclamó un RFC o CURP entre el momento en que validamos el tuyo y el momento en que escribimos. En modo lote, la transacción completa hace rollback — ningún empleado es creado. Reintenta la misma solicitud. ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos: | Codigo | Significado | | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `INVALID_INPUT` | Falla genérica de validación de esquema (formato, longitud, tipo). | | `DUPLICATE_RFC` | El RFC ya existe en otro empleado de la organización. | | `DUPLICATE_CURP` | El CURP ya existe en otro empleado de la organización. | | `DUPLICATE_RFC_IN_BATCH` | El mismo RFC aparece en más de una fila del lote. | | `DUPLICATE_CURP_IN_BATCH` | El mismo CURP aparece en más de una fila del lote. | | `DUPLICATE_INTERNAL_NUMBER` | El número interno ya lo tiene otro empleado de la organización (o se repite en el lote). Llega con estatus `409`. | | `LEGAL_ENTITY_NOT_FOUND` | `legal_entity_number` no coincide con ninguna razón social de la organización. | | `BELOW_MINIMUM_WAGE` | `daily_base_salary` es menor al salario mínimo diario vigente de la `minimum_wage_zone` del empleado. El mínimo se toma del catálogo `salario_minimo` de la organización y respeta el override de la razón social, así que puede variar entre razones sociales. | | `JOB_POSITION_NOT_FOUND` | `job_position_code` no coincide con un puesto activo. | | `BRANCH_NOT_FOUND` | `branch_code` no coincide con una sucursal activa. | | `DEPARTMENT_NOT_FOUND` | `department_code` no coincide con un departamento activo. | | `COST_CENTER_NOT_FOUND` | `cost_center_code` no coincide con un centro de costos activo. | | `EMPLOYER_REGISTRATION_NOT_FOUND` | `employer_registration` no coincide con un registro patronal de la razón social de la fila. | | `PAYROLL_CALENDAR_NOT_FOUND` | `payroll_calendar_number` no coincide con un calendario de nómina de la razón social de la fila. | | `CONTRACT_NOT_FOUND` | `contract_number` no coincide con un contrato vinculado a la razón social de la fila. | | `WORK_SCHEDULE_NOT_FOUND` | `work_schedule_number` no coincide con un horario activo de esta organización. | | `WORK_SCHEDULE_BUNDLE_NOT_FOUND` | `work_schedule_bundle_number` no coincide con una rotación activa de esta organización. | | `INVALID_WORK_SCHEDULE_BUNDLE_STEP` | `work_schedule_bundle_start_step` nombra un paso que la rotación no tiene, o la rotación no tiene pasos. | | `ORGANIZATIONAL_UNIT_NOT_FOUND` | Un número de `organizational_units` no coincide con una unidad organizacional activa. | | `ORGANIZATIONAL_UNIT_DUPLICATE_TYPE` | Se enviaron dos unidades del mismo tipo en `organizational_units`. | | `CUSTOM_FIELD_NOT_FOUND` | Una clave plana del cuerpo no corresponde a un campo nativo ni a una definición de campo personalizado activa. | | `CUSTOM_FIELD_INVALID_TYPE` | El valor enviado para un campo personalizado no coincide con el `type` declarado de la definición. | | `CUSTOM_FIELD_REQUIRED` | Una definición activa con `is_required_for_create: true` no fue enviada para esa fila. | | `INVALID_HOME_MUNICIPALITY` | `home_municipality_code` no pertenece a `home_state_code`, o se envió sin estado. | | `INVALID_FISCAL_MUNICIPALITY` | `fiscal_municipality_code` no pertenece a `domicile_state_code`, o se envió sin estado. No se valida si `fiscal_address_same_as_home` es `true`. | | `INVALID_BIRTH_MUNICIPALITY` | `birth_municipality_code` no pertenece a `birth_state_code`, o se envió sin estado. | | `INVALID_NATIONALITY` | `nationality` no está en el catálogo de nacionalidades. | | `ACTIVATION_INCOMPLETE` | `activate: true` en un empleado que no está listo para nómina; el mensaje enumera los datos faltantes. Llega con estatus `400`. | | `RETRO_CONFIRMATION_REQUIRED` | El alta cae en periodos de nómina ya cerrados. Reenvía con `confirm_retro: true`. Llega con estatus `409`. | | `CONCURRENT_MODIFICATION` | Señal de reintento por condición de carrera (respuesta `409`). | # Editar (/docs/developers/api-reference/employees/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente un empleado. Todos los campos del cuerpo son **opcionales** y operan en tres capas dentro de la misma transacción: **datos personales** del empleado, **datos laborales** (puesto, departamento, sucursal, registro patronal, etc.), y **valores de campos personalizados**. Omitir una clave significa que ese campo no se toca; `null` la limpia (cuando aplica). El identificador en la URL es el `employee_number`. Una solicitud exitosa devuelve `200` con el objeto **plano** del empleado (datos personales + los de su empleo actual, mismo shape que `POST` en modo single). Los empleados eliminados se tratan como no encontrados (`404`). **Sobre los datos laborales.** Solo pueden modificarse cuando el empleado está **activo** (no dado de baja). Si el empleado está dado de baja, tocar cualquier campo laboral devuelve `400` con `NO_OPEN_EMPLOYMENT` — primero debes [reactivarlo](/docs/developers/api-reference/employees/reactivate). **Invariante IMSS.** Cuando el valor **efectivo** de `contributes_to_imss` queda en `true`, el `employer_registration` efectivo debe estar puesto. Si no, la respuesta es `400`. **Datos fijos por historial.** Cuando el empleado ya tiene una nómina cerrada en la que se le pagó, o su alta ante el IMSS ya se presentó (desde la plataforma o fuera de ella), `worker_type`, `contributes_to_imss` y `employer_registration` ya no pueden cambiar: la respuesta es `409` con `LOCKED_BY_PAYROLL_HISTORY` en el campo. Esos datos cambian con una baja y un reingreso. `payroll_calendar_number` no puede cambiar mientras el empleado está en una nómina abierta (`409` con `LOCKED_BY_OPEN_PAYROLL`). Reenviar el valor guardado no cuenta como cambio. **Tipo de trabajador.** Cuando el `worker_type` efectivo no es `subordinate`, el `contributes_to_imss` efectivo debe ser `false` y los valores efectivos de `imss_number` y `employer_registration` deben quedar vacíos (sino, `400`): para reclasificar a un subordinado, envía esos campos en `null` en la misma solicitud. Reclasificar a `asimilado` u `honorarios` borra la fecha de antigüedad del empleado. Reclasificar a un trabajador no altera ninguna corrida cerrada: cada corrida congela el tipo con el que se calculó. Un `subordinate` con créditos INFONAVIT o FONACOT activos o suspendidos no puede pasar a `asimilado` ni a `honorarios` (`409` con `WORKER_TYPE_HAS_CREDITS`). **Compensación y estatus.** El endpoint acepta también la compensación (`daily_base_salary`, `regimen_fiscal`, `payroll_calendar_number`, `contract_number`), el horario de trabajo (`work_schedule_number` o `work_schedule_bundle_number`) y las unidades organizacionales (`organizational_units`) del empleado. Cambiar el sueldo registra una nueva fila con fecha efectiva en el historial de sueldos. `activate: true` da de alta al empleado solo si el estado fusionado queda listo para nómina (sino, `400` `ACTIVATION_INCOMPLETE`); si su fecha de ingreso cae en periodos cerrados, exige `confirm_retro: true` (sino, `409` `RETRO_CONFIRMATION_REQUIRED`) y paga esos periodos con ajustes retroactivos; `activate: false` lo deja en `pending`; si **omites** `activate`, un empleado `active` se **degrada** automáticamente a `pending` cuando una edición elimina un dato requerido para nómina. El arreglo `organizational_units` **reemplaza** el conjunto actual (un arreglo vacío lo limpia). ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/employees/{employee_number}` **Path param:** `employee_number` — entero positivo, el número de empleado dentro de la organización. ## Actualizar datos personales [#actualizar-datos-personales] *** Envía solo los campos que quieras cambiar. El resto del empleado (incluyendo sus datos laborales) queda intacto. ```json { "first_name": "María José", "notes": "Promovida en marzo." } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employees/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "first_name": "María José", "notes": "Promovida en marzo." }' ``` **Respuesta** ```json { "employee_number": 5, "internal_number": null, "first_name": "María José", "paternal_last_name": "González", "status": "active", "legal_entity_number": 1, "notes": "Promovida en marzo.", "...": "resto de campos planos del empleado y su empleo", "updated_at": "2026-05-25T11:15:32.000Z" } ``` ## Reasignar departamento y limpiar puesto [#reasignar-departamento-y-limpiar-puesto] *** Los campos laborales solo aplican si el empleado está activo. `null` desvincula (en este caso, deja al empleado sin puesto asignado). Si el empleado está dado de baja, la solicitud falla con `400` `NO_OPEN_EMPLOYMENT`. ```json { "department_code": "MKT", "job_position_code": null } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employees/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "department_code": "MKT", "job_position_code": null }' ``` **Respuesta** ```json { "employee_number": 5, "internal_number": null, "first_name": "María José", "paternal_last_name": "González", "status": "active", "legal_entity_number": 1, "department_code": "MKT", "job_position_code": null, "...": "resto de campos planos del empleado y su empleo", "updated_at": "2026-05-25T11:20:00.000Z" } ``` ## Alternar contribucion IMSS [#alternar-contribucion-imss] *** Cuando `contributes_to_imss` efectivo es `true`, el `employer_registration` efectivo debe estar puesto. Si lo dejas en `true` sin un registro patronal vinculado (ni enviado ni almacenado), la solicitud falla con `400`. ```json { "contributes_to_imss": false } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employees/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contributes_to_imss": false }' ``` **Respuesta** ```json { "employee_number": 5, "internal_number": null, "first_name": "María José", "paternal_last_name": "González", "status": "active", "legal_entity_number": 1, "contributes_to_imss": false, "...": "resto de campos planos del empleado y su empleo", "updated_at": "2026-05-25T11:25:00.000Z" } ``` ## Campos personalizados [#campos-personalizados] *** Los `key` de campos personalizados se envían como **claves planas en la raíz del cuerpo**, junto con cualquier campo nativo del empleado en el mismo request. Cada `key` enviada se **guarda** uno a uno: si ya tenía valor, se sobrescribe; si no lo tenía, se crea. `null` limpia ese valor específico. Las `key` **omitidas** del payload no se tocan — es un patch parcial sobre el conjunto de valores del empleado. ```json { "department_code": "MKT", "shirt_size": "L", "allergens": null } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employees/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "department_code": "MKT", "shirt_size": "L", "allergens": null }' ``` **Respuesta** ```json { "employee_number": 5, "internal_number": null, "first_name": "María José", "paternal_last_name": "González", "status": "active", "legal_entity_number": 1, "department_code": "MKT", "...": "resto de campos planos del empleado y su empleo", "updated_at": "2026-05-25T11:30:00.000Z" } ``` ## Campos del cuerpo — datos personales [#campos-del-cuerpo--datos-personales] | Campo | Tipo | Notas | | ----------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `first_name` | string | Mínimo 1 carácter. No aceptado como `null`. | | `middle_name` | string \| null | | | `paternal_last_name` | string | Mínimo 1 carácter. No aceptado como `null`. | | `maternal_last_name` | string \| null | | | `gender` | `male` \| `female` \| `unspecified` | | | `birth_date` | string `YYYY-MM-DD` \| null | | | `birth_country` | string \| null | | | `birth_state` | string \| null | Nombre del estado de nacimiento. Se acepta por compatibilidad: si coincide con un estado del catálogo se guarda su clave; si no, se conserva el texto. Prefiere `birth_state_code`. | | `birth_city` | string \| null | Nombre del municipio de nacimiento. Se acepta por compatibilidad: si coincide con un municipio del estado de nacimiento se guarda su clave; si no, se conserva el texto. Prefiere `birth_municipality_code`. | | `birth_state_code` | string \| null | Estado de nacimiento, clave SAT c\_Estado de 3 letras (por ejemplo `JAL`). | | `birth_municipality_code` | string \| null | Municipio de nacimiento, clave INEGI de 3 dígitos dentro del estado de nacimiento (por ejemplo `120`). Al cambiar el estado sin enviar municipio, el municipio se limpia. | | `birth_place` | string \| null | | | `nationality` | string \| null | Nacionalidad en gentilicio, del catálogo de nacionalidades (por ejemplo `Mexicana`). No distingue mayúsculas, acentos ni género, y acepta el nombre del país, la forma en inglés o la clave de tres letras: `MEXICANO`, `México` y `MEX` se guardan como `Mexicana`. Un valor fuera del catálogo responde `INVALID_NATIONALITY`. `null` la borra. | | `phone_number` | string \| null | Teléfono del empleado con clave de país y sin espacios, por ejemplo `+529611234567`. `null` lo borra. | | `secondary_phone_number` | string \| null | Teléfono secundario. Mismo formato que `phone_number`. `null` lo borra. | | `postal_code` | string `#####` \| null | Código postal de 5 dígitos del domicilio fiscal (el de la Constancia de Situación Fiscal), que es el que valida el CFDI. Cuenta para la preparación de nómina. Se ignora si la casilla queda en `true` (ve `fiscal_address_same_as_home`). | | `work_center_state_code` | string | Entidad del centro de trabajo, clave SAT c\_Estado de 3 letras (por ejemplo `CMX`). No acepta `null`: solo puede cambiarse por otra entidad válida. Cuenta para la preparación de nómina. | | `home_street` | string \| null | Calle del domicilio real (donde vive el empleado). Independiente del domicilio fiscal. | | `home_exterior_number` | string \| null | Número exterior del domicilio real. | | `home_interior_number` | string \| null | Número interior del domicilio real. | | `home_neighborhood` | string \| null | Colonia del domicilio real. | | `home_city` | string \| null | Nombre del municipio o alcaldía. Se acepta por compatibilidad: si coincide con un municipio del estado se guarda su clave; si no, se conserva el texto. Prefiere `home_municipality_code`. | | `home_municipality_code` | string \| null | Municipio o alcaldía del domicilio real, clave INEGI de 3 dígitos dentro de `home_state_code` (por ejemplo `039`). Al cambiar el estado sin enviar municipio, el municipio se limpia. | | `home_state_code` | string \| null | Estado del domicilio real, clave SAT c\_Estado de 3 letras (por ejemplo `NLE`). | | `home_postal_code` | string `#####` \| null | Código postal de 5 dígitos del domicilio real. Solo cuenta para la preparación de nómina si `fiscal_address_same_as_home` es `true`. `null` lo borra. | | `fiscal_address_same_as_home` | boolean | `true` copia el domicilio real al fiscal en cada edición e ignora `postal_code`, `domicile_state_code` y los `fiscal_*` enviados; mientras siga en `true`, cambiar un `home_*` cambia también el domicilio fiscal. Si se omite, conserva el valor guardado, salvo que envíes algún campo del domicilio fiscal: entonces pasa a `false`. | | `domicile_state_code` | string \| null | Estado del domicilio fiscal, clave SAT c\_Estado de 3 letras. Cambiarlo borra `fiscal_municipality_code` si no se envía uno nuevo. | | `fiscal_street` | string \| null | Calle del domicilio fiscal. | | `fiscal_exterior_number` | string \| null | Número exterior del domicilio fiscal. | | `fiscal_interior_number` | string \| null | Número interior del domicilio fiscal. | | `fiscal_neighborhood` | string \| null | Colonia del domicilio fiscal. | | `fiscal_municipality_code` | string \| null | Municipio o alcaldía del domicilio fiscal, clave INEGI de 3 dígitos dentro de `domicile_state_code`. | | `fiscal_city` | string \| null | Nombre del municipio del domicilio fiscal. Se acepta por compatibilidad igual que `home_city`. Prefiere `fiscal_municipality_code`. | | `internal_number` | string \| null | Número interno que asigna la organización. Único en la organización sin distinguir mayúsculas: `409` con `DUPLICATE_INTERNAL_NUMBER` si otro empleado ya lo tiene. `null` lo borra. | | `bank` | string \| null | Nombre del banco, máximo 60 caracteres. Texto libre; utiliza el nombre del catálogo SAT c\_Banco para que coincida con el selector de la plataforma. | | `clabe` | string `18 dígitos` \| null | CLABE interbancaria. Se valida el dígito verificador. | | `account_number` | string \| null | Número de cuenta, solo dígitos: máximo 12 si `bank` es BANREGIO, máximo 11 para cualquier otro banco. Sin una `clabe` válida, el `bank` efectivo es obligatorio: `400` con `BANK_REQUIRED_FOR_ACCOUNT`. Solo se revisa si la solicitud envía `bank`, `clabe` o `account_number`. | | `card_number` | string `16 dígitos` \| null | Número de tarjeta. | | `notes` | string \| null | | | `rfc` | string | 12 o 13 caracteres. Editable — se devuelve `409` si el nuevo valor colisiona con otro empleado de la organización. | | `curp` | string | Exactamente 18 caracteres. Editable — `409` en colisión. | ## Campos del cuerpo — datos laborales [#campos-del-cuerpo--datos-laborales] Aplican solo cuando el empleado está activo (no dado de baja). Si el empleado está dado de baja, **cualquier** campo de esta sección produce `400` `NO_OPEN_EMPLOYMENT`. | Campo | Tipo | Notas | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `job_position_code` | string \| null | | | `branch_code` | string \| null | | | `department_code` | string \| null | | | `cost_center_code` | string \| null | | | `employer_registration` | string \| null | Dentro de la razón social del empleado. Debe quedar en `null` cuando el `worker_type` efectivo no es `subordinate`. Fijo cuando el empleado tiene historial de nómina (`409` `LOCKED_BY_PAYROLL_HISTORY`). | | `imss_number` | string \| null | NSS de 11 dígitos cuando se asigna. Se valida el dígito verificador. Debe quedar en `null` cuando el `worker_type` efectivo no es `subordinate`. | | `is_included_in_payroll_calculations` | boolean | | | `contributes_to_imss` | boolean | Cuando el valor efectivo es `true`, el `employer_registration` efectivo debe estar puesto; sino `400`. Debe quedar en `false` cuando el `worker_type` efectivo no es `subordinate`. Fijo cuando el empleado tiene historial de nómina (`409` `LOCKED_BY_PAYROLL_HISTORY`). | | `worker_type` | `subordinate` \| `asimilado` \| `honorarios` | Tipo de trabajador para efectos estatutarios. No se infiere del tipo de contrato SAT. Fijo cuando el empleado tiene historial de nómina (`409` `LOCKED_BY_PAYROLL_HISTORY`); `409` `WORKER_TYPE_HAS_CREDITS` al dejar de ser `subordinate` con créditos INFONAVIT o FONACOT activos. | | `minimum_wage_zone` | `general` \| `northern_border` | Zona económica del empleado: Zona del Salario Mínimo General o Zona Libre de la Frontera Norte. Al cambiarla, el `daily_base_salary` efectivo se vuelve a validar contra el mínimo de la nueva zona. Cada corrida congela la zona con la que se calculó. | | `salary_type` | `fixed` \| `mixed` \| `variable` \| null | Tipo de salario del empleado: fijo, mixto o variable. Con `null` se borra. Es informativo: no cambia el cálculo de la nómina. | | `imss_worker_type` | `permanent` \| `eventual_city` \| `eventual_construction` \| `eventual_rural` \| null | Tipo de trabajador IMSS: permanente, eventual de la ciudad, eventual de la construcción o eventual del campo. Con `null` se borra. Obligatorio para un empleado activo que aporta al IMSS. Si el empleado no aporta al IMSS, se guarda vacío. | | `imss_work_schedule` | `full` \| `one_day_week` \| `two_day_week` \| `three_day_week` \| `four_day_week` \| `five_day_week` \| `reduced_day` \| null | Semana o jornada reducida: semana completa, semana reducida de 1 a 5 días o jornada reducida. Con `null` se borra. Obligatorio para un empleado activo que aporta al IMSS. Si el empleado no aporta al IMSS, se guarda vacío. | | `is_unionized` | boolean | Indica si el trabajador está sindicalizado. | | `receives_annual_isr_adjustment` | boolean | Si el subordinado entra al ajuste anual de ISR. `true` con un `worker_type` efectivo distinto de `subordinate` responde `400`. Reclasificar a `asimilado` u `honorarios` lo regresa a `false`, y reclasificar a `subordinate` lo vuelve `true` si no lo envías. | | `opts_into_annual_isr_adjustment` | boolean | **Obsoleto:** utiliza `receives_annual_isr_adjustment`. Un `true` se acepta como su equivalente; un `false` se ignora, porque antes no excluía a ningún subordinado. Si envías ambos, gana `receives_annual_isr_adjustment`. Las respuestas lo devuelven siempre en `false`: su significado era la solicitud de un asimilado o un trabajador por honorarios, que ya no existe. Antes un subordinado siempre entraba al ajuste; ahora `receives_annual_isr_adjustment` decide. Un error sobre este campo se reporta con el nombre que enviaste. | | `daily_base_salary` | number \| null | Sueldo diario base (> 0). Un cambio registra una nueva fila con fecha efectiva en el historial de sueldos. | | `regimen_fiscal` | string \| null | Clave SAT del régimen del trabajador (c\_TipoRegimen). El valor efectivo debe corresponder al `worker_type` efectivo: `02` o `13` para `subordinate`, del `05` al `11` para `asimilado` y `honorarios` (sino `400`). | | `work_shift_type` | string \| null | Clave SAT del tipo de jornada (c\_TipoJornada). Va en el recibo timbrado y cuenta para la preparación de nómina. | | `benefit_scheme_number` | null | Ya no existe: las prestaciones del empleado vienen de su contrato (`contract_number`). Se acepta `null` y se ignora; cualquier otro valor responde `400`. | | `payroll_calendar_number` | integer positivo \| null | Calendario de nómina; debe pertenecer a la razón social del empleado. No cambia mientras el empleado está en una nómina abierta (`409` `LOCKED_BY_OPEN_PAYROLL`). | | `contract_number` | integer positivo \| null | Contrato; debe estar vinculado a la razón social del empleado. | | `work_schedule_number` | integer positivo \| null | Horario semanal fijo. Debe ser un horario **activo** de la organización; `null` limpia la asignación. Excluyente con `work_schedule_bundle_number`. | | `work_schedule_bundle_number` | integer positivo \| null | Rotación de horarios. Debe ser una rotación **activa** de la organización; `null` limpia la asignación. Excluyente con `work_schedule_number`. | | `work_schedule_bundle_start_step` | integer positivo \| null | Paso del ciclo en el que arranca el empleado. **Opcional**: omitido, se utiliza el primer paso de la rotación. Un paso con valor exige `work_schedule_bundle_number` en el mismo cuerpo. | | `organizational_units` | integer\[] | **Reemplaza** el conjunto de unidades organizacionales (un arreglo vacío lo limpia). Máximo una unidad por tipo. | | `activate` | boolean | `true` da de alta (`active`) solo si el estado fusionado está listo para nómina (sino `400` `ACTIVATION_INCOMPLETE`); `false` deja en `pending`. Omitirlo aún degrada `active`→`pending` si la edición rompe la preparación. | | `confirm_retro` | boolean | Autoriza que dar de alta a un empleado `pending` cuya fecha de ingreso cae en periodos **cerrados** pague esos periodos con ajustes retroactivos. Sin él, esa alta responde `409` `RETRO_CONFIRMATION_REQUIRED` (con `retro_periods`) y no se guarda nada. | ## Campos del cuerpo — valores de campos personalizados [#campos-del-cuerpo--valores-de-campos-personalizados] Los valores de campos personalizados se envían como **claves planas en la raíz del cuerpo**, junto a los campos nativos del empleado. Cada `key` enviada se guarda (sobrescribe el valor anterior si existía); `null` limpia ese campo; las `key` omitidas del payload no se tocan. Los tipos aceptados son los mismos que en [crear](/docs/developers/api-reference/employees/create) (`text`/`select` → string, `number` → número, `boolean` → `true`/`false`, `date` → string `YYYY-MM-DD`). ## No aceptados [#no-aceptados] * `legal_entity_number` — la razón social del empleado activo es inmutable. Para cambiarla, primero da de baja al empleado y reactívalo contra una nueva razón social. * `hire_date`, `seniority_date` — se fijan al crear/reactivar. * `termination_date` — se fija vía [`/terminate`](/docs/developers/api-reference/employees/terminate). * `employee_number` — identificador en la URL. ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — el objeto **plano** del empleado (datos personales + los de su empleo actual), misma forma que la respuesta de [crear](/docs/developers/api-reference/employees/create) en modo single. ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `employee_number` inválido en la URL; falla de validación del cuerpo; `NO_OPEN_EMPLOYMENT` cuando se tocan campos laborales y el empleado está dado de baja; `*_NOT_FOUND` para códigos que no resuelven (incluye `PAYROLL_CALENDAR_NOT_FOUND`, `CONTRACT_NOT_FOUND`, `ORGANIZATIONAL_UNIT_NOT_FOUND`); `ORGANIZATIONAL_UNIT_DUPLICATE_TYPE`; `ACTIVATION_INCOMPLETE` al dar de alta un empleado que no está listo para nómina; `BELOW_MINIMUM_WAGE` cuando el `daily_base_salary` efectivo es menor al salario mínimo diario vigente de la `minimum_wage_zone` efectiva (tomado del catálogo `salario_minimo`, respetando el override de la razón social); `INVALID_HOME_MUNICIPALITY` / `INVALID_FISCAL_MUNICIPALITY` / `INVALID_BIRTH_MUNICIPALITY` cuando el municipio efectivo no pertenece al estado efectivo; `ACCOUNT_NUMBER_TOO_LONG` cuando el `account_number` efectivo excede los dígitos permitidos para el `bank` efectivo (12 para BANREGIO, 11 para cualquier otro); `BANK_REQUIRED_FOR_ACCOUNT` cuando el `account_number` efectivo no tiene `bank` ni una `clabe` válida; `CUSTOM_FIELD_NOT_FOUND` / `CUSTOM_FIELD_INVALID_TYPE`; invariante IMSS violada; `regimen_fiscal` que no corresponde al `worker_type` efectivo; `imss_number` o `employer_registration` efectivos en un empleado que no es `subordinate`. | | `404` | El empleado con ese `employee_number` no existe (o fue eliminado) en esta organización. | | `409` | `RETRO_CONFIRMATION_REQUIRED` al dar de alta a un empleado cuya fecha de ingreso cae en periodos cerrados sin `confirm_retro: true`; `LOCKED_BY_PAYROLL_HISTORY` al cambiar `worker_type`, `contributes_to_imss` o `employer_registration` de un empleado con historial de nómina; `LOCKED_BY_OPEN_PAYROLL` al cambiar `payroll_calendar_number` mientras está en una nómina abierta; `WORKER_TYPE_HAS_CREDITS` al dejar de ser `subordinate` con créditos INFONAVIT o FONACOT activos; `DUPLICATE_INTERNAL_NUMBER`; carrera por RFC/CURP duplicado durante la escritura (`CONCURRENT_MODIFICATION`). | # Listar (/docs/developers/api-reference/employees/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de los empleados pertenecientes a tu organización. Los resultados se devuelven en formato JSON e incluyen metadatos de paginación. Cada fila es un objeto **plano** con los datos personales del empleado **y** los de su empleo actual (razón social, estatus, fechas, asignaciones, compensación, calendario/contrato y unidades organizacionales). Los empleados eliminados se excluyen automáticamente. El tamaño de página es fijo en 20 resultados. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/employees` ## Consulta multiples empleados [#consulta-multiples-empleados] *** ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | --------- | ----------- | --------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/employees" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 42, "page": 1, "page_size": 20, "total_pages": 3, "next": "https://app.integranom.com/api/v1/employees?page=2", "previous": null, "results": [ { "employee_number": 1, "internal_number": null, "first_name": "María", "middle_name": null, "paternal_last_name": "González", "maternal_last_name": "Pérez", "gender": "female", "birth_date": "1990-01-01T00:00:00.000Z", "birth_country": "México", "birth_state": "CDMX", "birth_state_code": null, "birth_municipality_code": null, "birth_city": "Ciudad de México", "birth_place": "Ciudad de México", "nationality": "Mexicana", "phone_number": "+525512345678", "secondary_phone_number": null, "rfc": "GOPM900101AB1", "curp": "GOPM900101MDFNRR07", "postal_code": "06000", "work_center_state_code": "CMX", "home_street": null, "home_exterior_number": null, "home_interior_number": null, "home_neighborhood": null, "home_municipality_code": null, "home_city": null, "home_state_code": null, "home_postal_code": null, "fiscal_address_same_as_home": false, "domicile_state_code": null, "fiscal_street": null, "fiscal_exterior_number": null, "fiscal_interior_number": null, "fiscal_neighborhood": null, "fiscal_municipality_code": null, "fiscal_city": null, "bank": null, "clabe": null, "account_number": null, "card_number": null, "notes": null, "legal_entity_number": 1, "status": "active", "hire_date": "2026-01-15T00:00:00.000Z", "seniority_date": null, "termination_date": null, "job_position_code": "DEV", "branch_code": "CDMX", "department_code": "ING", "cost_center_code": "CC01", "employer_registration": "A1234567890", "imss_number": "12345678903", "contributes_to_imss": true, "worker_type": "subordinate", "minimum_wage_zone": "general", "imss_worker_type": "permanent", "imss_work_schedule": "full", "salary_type": "fixed", "is_unionized": false, "receives_annual_isr_adjustment": true, "opts_into_annual_isr_adjustment": false, "is_included_in_payroll_calculations": true, "daily_base_salary": "500.0000", "regimen_fiscal": "02", "work_shift_type": "01", "payroll_calendar_number": 2, "contract_number": 5, "work_schedule_number": 3, "work_schedule_bundle_number": null, "work_schedule_bundle_start_step": null, "organizational_units": [ { "organizational_unit_number": 10, "organizational_unit_type_key": "region" } ], "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Consulta un empleado especifico [#consulta-un-empleado-especifico] *** Se puede buscar un empleado particular por su número de empleado (filtro de coincidencia exacta). ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | ----------------- | ------- | --------- | ------- | ------------------------------------- | | `employee_number` | integer | No | - | Filtra por número de empleado exacto. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/employees?employee_number=1" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "employee_number": 1, "internal_number": null, "first_name": "María", "middle_name": null, "paternal_last_name": "González", "maternal_last_name": "Pérez", "gender": "female", "birth_date": "1990-01-01T00:00:00.000Z", "birth_country": "México", "birth_state": "CDMX", "birth_state_code": null, "birth_municipality_code": null, "birth_city": "Ciudad de México", "birth_place": "Ciudad de México", "nationality": "Mexicana", "phone_number": "+525512345678", "secondary_phone_number": null, "rfc": "GOPM900101AB1", "curp": "GOPM900101MDFNRR07", "postal_code": "06000", "work_center_state_code": "CMX", "home_street": null, "home_exterior_number": null, "home_interior_number": null, "home_neighborhood": null, "home_municipality_code": null, "home_city": null, "home_state_code": null, "home_postal_code": null, "fiscal_address_same_as_home": false, "domicile_state_code": null, "fiscal_street": null, "fiscal_exterior_number": null, "fiscal_interior_number": null, "fiscal_neighborhood": null, "fiscal_municipality_code": null, "fiscal_city": null, "bank": null, "clabe": null, "account_number": null, "card_number": null, "notes": null, "legal_entity_number": 1, "status": "active", "hire_date": "2026-01-15T00:00:00.000Z", "seniority_date": null, "termination_date": null, "job_position_code": "DEV", "branch_code": "CDMX", "department_code": "ING", "cost_center_code": "CC01", "employer_registration": "A1234567890", "imss_number": "12345678903", "contributes_to_imss": true, "worker_type": "subordinate", "minimum_wage_zone": "general", "imss_worker_type": "permanent", "imss_work_schedule": "full", "salary_type": "fixed", "is_unionized": false, "receives_annual_isr_adjustment": true, "opts_into_annual_isr_adjustment": false, "is_included_in_payroll_calculations": true, "daily_base_salary": "500.0000", "regimen_fiscal": "02", "work_shift_type": "01", "payroll_calendar_number": 2, "contract_number": 5, "work_schedule_number": 3, "work_schedule_bundle_number": null, "work_schedule_bundle_start_step": null, "organizational_units": [ { "organizational_unit_number": 10, "organizational_unit_type_key": "region" } ], "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------ | | `400` | Valor inválido en `employee_number`. | | `401` | API key ausente o inválida. | # Reactivar (/docs/developers/api-reference/employees/reactivate) ## Descripcion general [#descripcion-general] Este endpoint **reactiva** a un empleado que está dado de baja, registrando una nueva relación laboral. Se usa para **recontratar** empleados que fueron dados de baja previamente. La nueva relación laboral puede atarse a una razón social distinta a la anterior, y opcionalmente puede arrastrar la antigüedad acumulada. Si el empleado no tiene historial laboral previo, este endpoint no es el camino — usa [POST `/api/v1/employees`](/docs/developers/api-reference/employees/create) para registrarlo por primera vez. **Cálculo de antigüedad.** Si `keep_seniority` es `true`, la `seniority_date` toma la fecha más antigua de antigüedad encontrada en el historial del empleado. Si es `false`, la antigüedad **se reinicia** y `seniority_date` queda igual a `rehire_date`. Un asimilado o un trabajador por honorarios no lleva antigüedad: su `seniority_date` queda en `null` sin importar `keep_seniority`. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/employees/{employee_number}/reactivate` **Path param:** `employee_number` — entero positivo, el número de empleado dentro de la organización. ## Recontratar con antiguedad [#recontratar-con-antiguedad] *** `keep_seniority: true` arrastra la fecha de antigüedad más antigua del historial. En este ejemplo, el empleado fue contratado originalmente el `2026-01-15` y se recontrata el `2026-08-01` conservando esa antigüedad. ```json { "legal_entity_number": 1, "rehire_date": "2026-08-01", "keep_seniority": true, "is_included_in_payroll_calculations": true, "contributes_to_imss": true, "imss_number": "12345678903", "already_registered_with_imss": false } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employees/5/reactivate" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "legal_entity_number": 1, "rehire_date": "2026-08-01", "keep_seniority": true, "is_included_in_payroll_calculations": true, "contributes_to_imss": true, "imss_number": "12345678903", "already_registered_with_imss": false }' ``` **Respuesta** ```json { "employee_number": 5, "legal_entity_number": 1, "status": "active", "hire_date": "2026-08-01T00:00:00.000Z", "seniority_date": "2026-01-15T00:00:00.000Z", "termination_date": null, "created_at": "2026-08-01T10:00:00.000Z" } ``` ## Recontratar reiniciando antiguedad y cambiando razon social [#recontratar-reiniciando-antiguedad-y-cambiando-razon-social] *** `keep_seniority: false` reinicia la antigüedad — la nueva `seniority_date` queda igual a `rehire_date`. La razón social puede ser diferente a la que tenía antes de la baja. ```json { "legal_entity_number": 2, "rehire_date": "2026-08-01", "keep_seniority": false, "is_included_in_payroll_calculations": true, "contributes_to_imss": false } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employees/5/reactivate" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "legal_entity_number": 2, "rehire_date": "2026-08-01", "keep_seniority": false, "is_included_in_payroll_calculations": true, "contributes_to_imss": false }' ``` **Respuesta** ```json { "employee_number": 5, "legal_entity_number": 2, "status": "active", "hire_date": "2026-08-01T00:00:00.000Z", "seniority_date": "2026-08-01T00:00:00.000Z", "termination_date": null, "created_at": "2026-08-01T10:00:00.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Requerido | Notas | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `legal_entity_number` | integer positivo | Sí | Razón social del empleado reactivado. Puede diferir de la que tenía antes de la baja. | | `rehire_date` | string `YYYY-MM-DD` | Sí | Nueva `hire_date` del empleado. | | `keep_seniority` | boolean | Sí | Si es `true`, `seniority_date` toma la fecha más antigua del historial. Si es `false`, se reinicia a `rehire_date`. | | `is_included_in_payroll_calculations` | boolean | Sí | | | `contributes_to_imss` | boolean | Sí | Debe ser `false` cuando `worker_type` no es `subordinate`. | | `worker_type` | `subordinate` \| `asimilado` \| `honorarios` | No | Si lo omites, el reingreso conserva el tipo del empleo anterior, y toma `subordinate` solo cuando no hay uno previo. | | `minimum_wage_zone` | `general` \| `northern_border` | No | Si lo omites, el reingreso conserva la zona del empleo anterior, y toma `general` solo cuando no hay uno previo. | | `salary_type` | `fixed` \| `mixed` \| `variable` | No | Si lo omites, el reingreso conserva el tipo de salario del empleo anterior, y queda vacío cuando no hay uno. | | `imss_worker_type` | `permanent` \| `eventual_city` \| `eventual_construction` \| `eventual_rural` \| null | No | Si lo omites, el reingreso conserva el tipo de trabajador IMSS del empleo anterior, y es obligatorio si el reingreso aporta al IMSS y el empleo anterior no lo tiene (`IMSS_AFFILIATION_REQUIRED`). Si el reingreso no aporta al IMSS, siempre queda vacío. | | `imss_work_schedule` | `full` \| `one_day_week` \| `two_day_week` \| `three_day_week` \| `four_day_week` \| `five_day_week` \| `reduced_day` \| null | No | Si lo omites, el reingreso conserva la semana o jornada reducida del empleo anterior, y es obligatoria si el reingreso aporta al IMSS y el empleo anterior no la tiene (`IMSS_AFFILIATION_REQUIRED`). Si el reingreso no aporta al IMSS, siempre queda vacía. | | `work_shift_type` | string | No | Clave SAT del tipo de jornada (c\_TipoJornada). Si lo omites, el reingreso conserva la del empleo anterior. | | `is_unionized` | boolean | No | Si lo omites, el reingreso conserva el valor del empleo anterior, y toma `false` solo cuando no hay uno previo. | | `imss_number` | string | No | NSS de 11 dígitos. Se valida el dígito verificador. Solo para `subordinate`: con otro `worker_type` efectivo responde `400`. | | `already_registered_with_imss` | boolean | No | Por defecto `false`. Con `true`, el alta del reingreso en Movimientos IMSS se registra como ya presentada ante el IMSS fuera de la plataforma: no queda pendiente ni se calcula su SBC. Con `false`, queda pendiente de presentar. Solo tiene efecto cuando `contributes_to_imss` es `true`. | | `confirm_retro` | boolean | No | Autoriza que un reingreso con fecha dentro de un periodo **cerrado** se registre como ajuste retroactivo. Ver abajo. | ## Comportamiento [#comportamiento] 1. Carga el empleado y su historial laboral completo. 2. Rechaza con `400` `ALREADY_HAS_OPEN_EMPLOYMENT` si el empleado está activo (no dado de baja) — primero hay que darlo de baja con [`/terminate`](/docs/developers/api-reference/employees/terminate). 3. Rechaza con `400` `NO_PRIOR_EMPLOYMENT` si el empleado **nunca** ha sido registrado — usa [POST `/api/v1/employees`](/docs/developers/api-reference/employees/create) para el primer alta. 4. Resuelve `legal_entity_number` contra una razón social existente (`LEGAL_ENTITY_NOT_FOUND` si no resuelve). 5. Calcula `seniority_date` según la regla descrita arriba. 6. Marca al empleado como activo nuevamente. 7. Si `contributes_to_imss` es `true`, genera el alta del reingreso en Movimientos IMSS: pendiente de presentar, o ya presentada si envías `already_registered_with_imss: true`. ## Forma de la respuesta [#forma-de-la-respuesta] `201 Created` — los datos laborales del empleado reactivado: ```json { "employee_number": 5, "legal_entity_number": 1, "status": "active", "hire_date": "2026-08-01T00:00:00.000Z", "seniority_date": "2026-01-15T00:00:00.000Z", "termination_date": null, "created_at": "2026-08-01T10:00:00.000Z" } ``` ## Reingreso con fecha dentro de un periodo cerrado [#reingreso-con-fecha-dentro-de-un-periodo-cerrado] *** Un reingreso fechado **dentro** de un periodo ya cerrado afecta una nómina que ya se pagó. Igual que en la [baja](/docs/developers/api-reference/employees/terminate), no se aplica en silencio: la llamada se rechaza con `409 RETRO_CONFIRMATION_REQUIRED` (con `retro_periods`, los periodos afectados). Reenvíala con **`confirm_retro: true`** para aceptar. El ajuste retroactivo se genera cuando el nuevo empleo queda listo para nómina (con calendario, sueldo y contrato asignados). ## Errores [#errores] | Status | Código | Causa | | ------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | — | `employee_number` inválido en la URL o falla de validación del cuerpo. | | `400` | `NO_PRIOR_EMPLOYMENT` | El empleado nunca ha sido registrado — usa `POST /api/v1/employees`. | | `400` | `ALREADY_HAS_OPEN_EMPLOYMENT` | El empleado está activo — primero dalo de baja con `/terminate`. | | `400` | `WORKER_TYPE_FIELD_CONFLICT` | Se envió `imss_number` para un reingreso cuyo `worker_type` efectivo no es `subordinate`. | | `400` | `IMSS_AFFILIATION_REQUIRED` | El reingreso aporta al IMSS y `imss_worker_type` o `imss_work_schedule` queda vacío: no se envió y el empleo anterior no lo tiene, o se envió en `null`. | | `400` | `LEGAL_ENTITY_NOT_FOUND` | `legal_entity_number` no coincide con ninguna razón social de la organización. | | `404` | — | El empleado no existe o ya fue eliminado en esta organización. | | `409` | `RETRO_CONFIRMATION_REQUIRED` | La fecha de reingreso cae en un periodo cerrado. Reenvía con `confirm_retro: true`. | # Baja (/docs/developers/api-reference/employees/terminate) ## Descripcion general [#descripcion-general] Este endpoint **da de baja** al empleado: marca su estado como `terminated` y guarda la `termination_date` enviada. El empleado **no** se elimina — su registro personal se conserva y queda en el historial. Para volver a contratarlo, llama a [`/reactivate`](/docs/developers/api-reference/employees/reactivate). La razón social, fecha de alta y demás datos laborales se conservan tal cual para fines históricos (declaraciones SUA, reportes, etc.). ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/employees/{employee_number}/terminate` **Path param:** `employee_number` — entero positivo, el número de empleado dentro de la organización. ## Solicitud [#solicitud] *** Envía la fecha de baja. La API marca al empleado como `terminated` y guarda la `termination_date`. ```json { "termination_date": "2026-06-30" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employees/5/terminate" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "termination_date": "2026-06-30" }' ``` **Respuesta** ```json { "employee_number": 5, "status": "terminated", "hire_date": "2026-01-15T00:00:00.000Z", "seniority_date": "2026-01-15T00:00:00.000Z", "termination_date": "2026-06-30T00:00:00.000Z", "created_at": "2026-01-15T00:00:00.000Z", "retros_touched": 0 } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Requerido | Notas | | ------------------------------ | ------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `termination_date` | string `YYYY-MM-DD` | Sí | Fecha en que termina la relación laboral. | | `already_registered_with_imss` | boolean | No | Por defecto `false`. Con `true`, la baja en Movimientos IMSS se registra como ya presentada ante el IMSS fuera de la plataforma, sin fecha límite pendiente. Con `false`, queda pendiente de presentar. Solo tiene efecto si el empleado aporta al IMSS. | | `confirm_retro` | boolean | No | Autoriza que una baja con fecha dentro de un periodo **cerrado** se registre como ajuste retroactivo. Ver abajo. | ## Comportamiento [#comportamiento] 1. Carga al empleado activo (`status != "terminated"`). 2. Si la fecha de baja cae dentro de un periodo **cerrado**, exige `confirm_retro` (ver abajo). 3. Asigna `status = "terminated"` y `terminationDate = termination_date`. 4. Si el empleado aporta al IMSS, genera su baja en Movimientos IMSS: pendiente de presentar, o ya presentada si envías `already_registered_with_imss: true`. 5. Genera los ajustes retroactivos que correspondan y los reporta en `retros_touched`. 6. Los demás datos laborales (razón social, fecha de alta, FKs) se conservan para el historial. ## Baja con fecha dentro de un periodo cerrado [#baja-con-fecha-dentro-de-un-periodo-cerrado] *** Cerrar un periodo es definitivo. Una baja fechada **dentro** de sus fechas contradice los recibos ya pagados: el empleado cobró días que, con la nueva fecha, no le correspondían. Por eso la baja no se aplica en silencio. Si la fecha cae en un periodo cerrado, la llamada se rechaza con `409 RETRO_CONFIRMATION_REQUIRED` (con `retro_periods`, los periodos afectados). Reenvíala con **`confirm_retro: true`** para aceptar: la baja se registra y la diferencia se corrige con un **ajuste retroactivo negativo** en el siguiente periodo — la respuesta lo reporta en `retros_touched`. Es la misma regla que aplica la plataforma; el API no la puede saltar. ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — los datos laborales del empleado recién dado de baja: ```json { "employee_number": 5, "status": "terminated", "hire_date": "2026-01-15T00:00:00.000Z", "seniority_date": "2026-01-15T00:00:00.000Z", "termination_date": "2026-06-30T00:00:00.000Z", "created_at": "2026-01-15T00:00:00.000Z", "retros_touched": 0 } ``` ## Errores [#errores] | Status | Código | Causa | | ------ | ----------------------------- | ---------------------------------------------------------------------------------------------- | | `400` | — | `employee_number` inválido en la URL (no es entero positivo) o falla de validación del cuerpo. | | `400` | `NO_OPEN_EMPLOYMENT` | El empleado ya está dado de baja. | | `404` | — | El empleado no existe o ya fue eliminado en esta organización. | | `409` | `RETRO_CONFIRMATION_REQUIRED` | La fecha de baja cae en un periodo cerrado. Reenvía con `confirm_retro: true`. | # Crear (/docs/developers/api-reference/job-positions/create) ## Descripcion general [#descripcion-general] Este endpoint permite crear uno o varios puestos dentro de tu organización. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. El único campo obligatorio por fila es `name`; el resto son opcionales. Una solicitud exitosa devuelve `201` con los puestos creados, incluyendo los campos generados por el servidor (`id`, `job_position_number`, `created_at`, `updated_at`). A diferencia de los departamentos, los puestos no tienen jerarquía: no existen `parent_id` ni `depth`, es una creación plana. En modo lote, si **cualquier** fila falla la validación, **ningún** puesto se crea — la respuesta lista los problemas por fila para corregirlos en una sola pasada. Las bandas salariales (`salary_band_min`, `salary_band_max`) se aceptan como números JSON en el cuerpo, pero se serializan como **strings decimales** en la respuesta (por ejemplo `"15000.00"`) para preservar la precisión. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/job-positions` ## Solicitud unica — minima [#solicitud-unica--minima] *** `name` es el único campo requerido. `is_active` toma el valor `true` por defecto y `sort_order` toma `0`. Los demás campos quedan en `null`. ```json { "name": "Vendedor" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/job-positions" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Vendedor" }' ``` **Respuesta** ```json { "id": "ck...", "job_position_number": 5, "name": "Vendedor", "code": null, "description": null, "salary_band_min": null, "salary_band_max": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## Solicitud unica — todos los campos [#solicitud-unica--todos-los-campos] *** El siguiente ejemplo utiliza todos los campos aceptados. Si envías ambas bandas salariales, debe cumplirse que `salary_band_min ≤ salary_band_max` (si no, la respuesta es `400` con el campo `salary_band_max`). Observa cómo las bandas se devuelven como strings decimales (`"15000.00"`) aunque se envíen como números (`15000`). ```json { "name": "Gerente de Ventas", "code": "GER-VEN", "description": "Gerencia comercial", "salary_band_min": 15000, "salary_band_max": 35000, "is_active": true, "sort_order": 0 } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/job-positions" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Gerente de Ventas", "code": "GER-VEN", "description": "Gerencia comercial", "salary_band_min": 15000, "salary_band_max": 35000, "is_active": true, "sort_order": 0 }' ``` **Respuesta** ```json { "id": "ck...", "job_position_number": 5, "name": "Gerente de Ventas", "code": "GER-VEN", "description": "Gerencia comercial", "salary_band_min": "15000.00", "salary_band_max": "35000.00", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ## Solicitud por lote (1–100 puestos) [#solicitud-por-lote-1100-puestos] *** Envía un **arreglo** de hasta 100 puestos en el mismo endpoint. Si **cualquier** fila falla la validación, **ningún** puesto se crea y la respuesta lista los problemas por fila. ```json [ { "name": "Vendedor", "code": "VEN" }, { "name": "Cajero", "code": "CAJ" }, { "name": "Almacenista", "code": "ALM", "salary_band_min": 8000, "salary_band_max": 12000 } ] ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/job-positions" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "name": "Vendedor", "code": "VEN" }, { "name": "Cajero", "code": "CAJ" }, { "name": "Almacenista", "code": "ALM", "salary_band_min": 8000, "salary_band_max": 12000 } ]' ``` **Respuesta** ```json { "summary": { "total": 3, "created": 3 }, "created": [ { "id": "ck...", "job_position_number": 6, "name": "Vendedor", "code": "VEN", "description": null, "salary_band_min": null, "salary_band_max": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" }, { "id": "ck...", "job_position_number": 7, "name": "Cajero", "code": "CAJ", "description": null, "salary_band_min": null, "salary_band_max": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" }, { "id": "ck...", "job_position_number": 8, "name": "Almacenista", "code": "ALM", "description": null, "salary_band_min": "8000.00", "salary_band_max": "12000.00", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ] } ``` ## Reglas de los campos [#reglas-de-los-campos] | Campo | Tipo | Requerido | Notas | | ----------------- | ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------ | | `name` | string | Sí | No vacío tras recortar espacios. | | `code` | string | No | ≤ 60 caracteres, se almacena en mayúsculas, único por organización. | | `description` | string | No | Texto libre. | | `salary_band_min` | number ≥ 0 \| null | No | Banda salarial inferior (MXN). Por defecto `null`. Se acepta como número en la entrada; se devuelve como string decimal. | | `salary_band_max` | number ≥ 0 \| null | No | Banda salarial superior (MXN). Por defecto `null`. Si envías ambas bandas, debe cumplirse `salary_band_min ≤ salary_band_max`. | | `is_active` | boolean | No | Por defecto `true`. | | `sort_order` | integer ≥ 0 | No | Por defecto `0`. | El puesto no lleva clase de riesgo IMSS: si envías `risk_class`, con cualquier valor, la respuesta es `400` con ese campo. La clase de riesgo se captura en el [registro patronal](/docs/developers/api-reference/employer-registrations/create). ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — solicitud única [#201--solicitud-única] Cuando el cuerpo es **un objeto**, la respuesta es el objeto del puesto creado. ```json { "id": "ck...", "job_position_number": 5, "name": "Gerente de Ventas", "code": "GER-VEN", "description": "Gerencia comercial", "salary_band_min": "15000.00", "salary_band_max": "35000.00", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ``` ### `201` — solicitud por lote [#201--solicitud-por-lote] Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 3, "created": 3 }, "created": [ { "id": "ck...", "job_position_number": 6, "name": "Vendedor", "...": "..." }, { "id": "ck...", "job_position_number": 7, "name": "Cajero", "...": "..." }, { "id": "ck...", "job_position_number": 8, "name": "Almacenista", "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única] Para fallas de validación de esquema (campo faltante, tipo incorrecto), `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "name": ["Ingresa el nombre."] } } ``` ### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote] Reporte por fila. **Ningún** puesto del lote fue creado. ```json { "message": "Validation failed for 2 job position(s). No job positions were created.", "summary": { "total": 3, "with_errors": 2 }, "errors": [ { "index": 0, "job_position": { "name": "Vendedor", "code": "VEN" }, "issues": [ { "field": "code", "code": "DUPLICATE_CODE", "message": "A job position with code 'VEN' already exists in this organization." } ] }, { "index": 2, "job_position": { "name": "Cobranza", "code": "VEN" }, "issues": [ { "field": "code", "code": "DUPLICATE_CODE_IN_BATCH", "message": "Code 'VEN' appears multiple times in this batch." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `job_position.name` / `job_position.code` — extraídos de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. ### `409` — condición de carrera [#409--condición-de-carrera] Otra solicitud reclamó un `code` entre el momento en que validamos el tuyo y el momento en que escribimos. En modo lote, la transacción completa hace rollback — ningún puesto es creado. Reintenta la misma solicitud. ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos: | Codigo | Significado | | ------------------------- | -------------------------------------------------------------------------------------------------------- | | `INVALID_INPUT` | Falla genérica de validación de esquema (formato, longitud, tipo, orden de las bandas salariales, etc.). | | `DUPLICATE_CODE` | El `code` ya existe en otro puesto de la organización. | | `DUPLICATE_CODE_IN_BATCH` | El mismo `code` aparece en más de una fila del lote. | | `CONCURRENT_MODIFICATION` | Señal de reintento por condición de carrera (respuesta `409`). | # Eliminar (/docs/developers/api-reference/job-positions/delete) ## Descripcion general [#descripcion-general] Este endpoint realiza el **borrado** del puesto: a partir de ese momento deja de aparecer en `GET /api/v1/job-positions` y se trata como no encontrada en `PATCH`/`DELETE` (`404`). La respuesta exitosa es `204 No Content` con cuerpo vacío. A diferencia de departamentos y sucursales, los puestos **no forman un árbol**, así que no se verifica que tengan hijos vivos. Al eliminar el puesto, se **desvincula de cada empleo** que lo referenciaba: su `job_position_code` queda en `null` (misma transacción), de modo que ningún empleo queda apuntando a un puesto eliminado. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/job-positions/{job_position_number}` **Path param:** `job_position_number` — entero positivo, el número de puesto dentro de la organización. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/job-positions/5" \ -H "Authorization: Bearer TU_API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] * Marca el puesto como eliminado. * La fila deja de devolverse por `GET /api/v1/job-positions`. * Sin verificación de hijos (los puestos no forman árbol). Sí se **desvincula** el puesto de cada empleo que lo referenciaba: su `job_position_code` queda en `null`. ## Errores [#errores] | Status | Causa | | ------ | -------------------------------------------------------------------- | | `400` | `job_position_number` inválido en la URL. | | `404` | El puesto no existe o ya fue eliminado en esta organización. | | `409` | El puesto tiene plazas sin cancelar. Cancélalas antes de eliminarlo. | # Editar (/docs/developers/api-reference/job-positions/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente un puesto. Todos los campos del cuerpo son **opcionales**: si omites una clave, ese campo no se toca; si la envías como `null`, ese campo se limpia (cuando aplica). El identificador en la URL es el `job_position_number` del puesto dentro de tu organización. Una solicitud exitosa devuelve `200` con la forma completa del puesto actualizado. Los puestos eliminados se tratan como no encontrados (`404`). **Validación de las bandas salariales contra los valores efectivos:** la restricción `salary_band_min ≤ salary_band_max` se valida contra los **valores efectivos** tras aplicar el patch. Si tocas solo `salary_band_min`, se usa el `salary_band_max` actual de la base de datos para verificar la coherencia; lo mismo en sentido contrario. Si la comparación falla, la respuesta es `400` con el campo `salary_band_max`. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/job-positions/{job_position_number}` **Path param:** `job_position_number` — entero positivo, el número de puesto dentro de la organización. ## Actualizacion minima [#actualizacion-minima] *** Envía solo los campos que quieras cambiar. El resto del puesto queda intacto. ```json { "name": "Gerente Comercial" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/job-positions/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Gerente Comercial" }' ``` **Respuesta** ```json { "id": "ck...", "job_position_number": 5, "name": "Gerente Comercial", "code": "GER-VEN", "description": "Gerencia comercial", "salary_band_min": "15000.00", "salary_band_max": "35000.00", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T11:15:32.000Z" } ``` ## Limpiar campos [#limpiar-campos] *** `null` (y `""` para strings) limpia el campo. Por ejemplo, para quitar el `code` del puesto: ```json { "code": null } ``` Lo mismo aplica a `description`, `salary_band_min` y `salary_band_max`: envíalos como `null` para limpiarlos. **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/job-positions/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": null }' ``` **Respuesta** ```json { "id": "ck...", "job_position_number": 5, "name": "Gerente Comercial", "code": null, "description": "Gerencia comercial", "salary_band_min": "15000.00", "salary_band_max": "35000.00", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T11:18:09.000Z" } ``` ## Marcar como inactivo [#marcar-como-inactivo] *** `is_active` lo controla el usuario y es **independiente del borrado**: un puesto puede estar inactivo y seguir apareciendo en `GET /api/v1/job-positions`. Al desactivarlo (`is_active: false`) se **desvincula el puesto de cada empleo** que lo referenciaba (su `job_position_code` queda en `null`). Para retirarlo del listado, usa [DELETE](/docs/developers/api-reference/job-positions/delete). ```json { "is_active": false } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/job-positions/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "is_active": false }' ``` **Respuesta** ```json { "id": "ck...", "job_position_number": 5, "name": "Gerente Comercial", "code": null, "description": "Gerencia comercial", "salary_band_min": "15000.00", "salary_band_max": "35000.00", "is_active": false, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T11:22:44.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | ----------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | No vacío tras recortar. | | `code` | string \| null | `null` o `""` limpian el código. Se almacena en mayúsculas; debe ser único por organización. | | `description` | string \| null | `null` o `""` limpian la descripción. | | `salary_band_min` | number ≥ 0 \| null | `null` limpia la banda inferior. Se devuelve como string decimal. | | `salary_band_max` | number ≥ 0 \| null | `null` limpia la banda superior. La coherencia `min ≤ max` se valida contra los valores efectivos tras aplicar el patch. | | `is_active` | boolean | Flag activo controlado por el usuario. Independiente del borrado. Al ponerlo en `false`, se **desvincula el puesto de cada empleo** (`job_position_code → null`). | | `sort_order` | integer ≥ 0 | | Si envías `risk_class`, con cualquier valor, la respuesta es `400` con ese campo: la clase de riesgo IMSS se captura en el [registro patronal](/docs/developers/api-reference/employer-registrations/edit). ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — misma forma que la respuesta de [crear](/docs/developers/api-reference/job-positions/create) en modo single. ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `job_position_number` inválido en la URL; falla de validación del cuerpo (incluido `risk_class`); incoherencia en las bandas salariales (`min > max` efectivo). | | `404` | El puesto con ese `job_position_number` no existe (o fue eliminado) en esta organización. | | `409` | Carrera por `code` duplicado durante la escritura; o `is_active: false` en un puesto con plazas sin cancelar; o una banda salarial que deja fuera el salario diario autorizado por 30 de alguna plaza sin cancelar. | # Listar (/docs/developers/api-reference/job-positions/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de los puestos pertenecientes a tu organización. Los resultados se devuelven en formato JSON e incluyen metadatos de paginación. Los puestos eliminados se excluyen automáticamente. El tamaño de página es fijo en 20 resultados. Las bandas salariales (`salary_band_min`, `salary_band_max`) se serializan como **strings decimales** (por ejemplo `"15000.00"`) para preservar la precisión. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/job-positions` ## Consulta multiples puestos [#consulta-multiples-puestos] *** ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | --------- | --------------------- | --------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/job-positions" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 24, "page": 1, "page_size": 20, "total_pages": 2, "next": "https://app.integranom.com/api/v1/job-positions?page=2", "previous": null, "results": [ { "id": "ck...", "job_position_number": 1, "name": "Gerente de Ventas", "code": "GER-VEN", "description": "Gerencia comercial", "salary_band_min": "15000.00", "salary_band_max": "35000.00", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ] } ``` ## Consulta un puesto especifico [#consulta-un-puesto-especifico] *** Se puede buscar un puesto particular por su número, nombre o código. Los tres filtros hacen coincidencia exacta sin distinguir mayúsculas y minúsculas: el nombre y el código se almacenan en mayúsculas y el filtro se compara también en mayúsculas. ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | --------------------- | ------- | --------- | ------- | ---------------------------------------------------- | | `job_position_number` | integer | No | - | Filtra por número de puesto exacto. | | `name` | string | No | - | Filtra por nombre exacto. | | `code` | string | No | - | Filtra por código exacto (almacenado en mayúsculas). | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/job-positions?job_position_number=1" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "id": "ck...", "job_position_number": 1, "name": "Gerente de Ventas", "code": "GER-VEN", "description": "Gerencia comercial", "salary_band_min": "15000.00", "salary_band_max": "35000.00", "is_active": true, "sort_order": 0, "created_at": "2026-05-24T10:00:00.000Z", "updated_at": "2026-05-24T10:00:00.000Z" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | --------------------------------------------------- | | `400` | Valor inválido en `job_position_number` o `active`. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso `getJobPositions`. | # Listar (/docs/developers/api-reference/incidence-types/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado del catálogo de tipos de incidencia. El catálogo es **fijo y administrado por la plataforma**: es el mismo para todas las organizaciones, no está ligado a una razón social y no se puede crear, editar ni eliminar desde la API. Su `code` es único en todo el catálogo y es el identificador que usas al capturar incidencias. Los resultados se devuelven en formato JSON e incluyen metadatos de paginación. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/incidence-types` ## Consulta multiples tipos de incidencia [#consulta-multiples-tipos-de-incidencia] *** Devuelve todo el catálogo, paginado de 20 en 20. Puedes acotar el listado por categoría, por unidad, por su mapeo a nómina o por su flag activo. ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | ----------------- | ---------------------------- | --------- | ------- | ----------------------------------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `category` | `TIME` \| `MONETARY` | No | - | Filtra por categoría del tipo. | | `unit` | `days` \| `hours` \| `count` | No | - | Filtra por la unidad de captura. | | `payroll_mapping` | string | No | - | Filtra por el mapeo a nómina (por ejemplo `faltas`, `overtime_double_hours`). | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/incidence-types" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 8, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "code": "FALTA", "name": "Falta injustificada", "category": "TIME", "unit": "days", "payroll_mapping": "faltas", "is_paid": false, "is_active": true, "sort_order": 10, "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" } ] } ``` ## Consulta un tipo de incidencia por codigo [#consulta-un-tipo-de-incidencia-por-codigo] *** Busca un tipo de incidencia particular por su código con `code`. La coincidencia es exacta. ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | --------- | ------ | --------- | ------- | --------------------------------------------------- | | `code` | string | No | - | Filtra por el código exacto del tipo de incidencia. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/incidence-types?code=FALTA" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "code": "FALTA", "name": "Falta injustificada", "category": "TIME", "unit": "days", "payroll_mapping": "faltas", "is_paid": false, "is_active": true, "sort_order": 10, "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" } ] } ``` ## Campos de cada fila [#campos-de-cada-fila] | Campo | Tipo | Descripcion | | ----------------- | ---------------------------------- | ------------------------------------------------------------------------------------- | | `code` | string | Código del tipo. Único en todo el catálogo; es el que usas para capturar incidencias. | | `name` | string | Nombre legible del tipo. | | `category` | `"TIME"` \| `"MONETARY"` | Categoría del tipo. Sólo los `TIME` se capturan en el ledger de asistencia. | | `unit` | `"days"` \| `"hours"` \| `"count"` | Unidad en la que se captura la incidencia (días, horas u ocurrencias). | | `payroll_mapping` | string | Columna de nómina a la que se acumula la incidencia; `none` si es informativa. | | `is_paid` | boolean | `true` si la incidencia es con goce de sueldo. | | `is_active` | boolean | Flag activo del tipo. | | `sort_order` | integer | Orden de despliegue dentro del catálogo. | | `created_at` | string (ISO 8601) | Fecha de creación del tipo. | | `updated_at` | string (ISO 8601) | Última modificación del tipo. | ## Errores [#errores] | Status | Causa | | ------ | ---------------------------------------------------------------------------------- | | `400` | Valor inválido en algún filtro (`category`, `unit`, `payroll_mapping` o `active`). | | `401` | API key ausente o inválida. | # Crear (/docs/developers/api-reference/legal-entities/create) ## Descripcion general [#descripcion-general] Este endpoint permite crear una o varias razones sociales dentro de tu organización. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. Una solicitud exitosa devuelve `201` con la(s) razón(es) social(es) creada(s), incluyendo los campos generados por el servidor (`legal_entity_number`, `created_at`, `updated_at`). `legal_entity_number` lo asigna el servidor: **no lo envíes**. En modo lote, si **cualquier** fila falla la validación, **ninguna** razón social se crea — la respuesta lista los problemas por fila para corregirlos en una sola pasada. **Preparación para nómina.** Al crear cada razón social se le vinculan automáticamente los **valores de nómina compartidos** de la organización: el catálogo de conceptos estándar y el contrato base, que se crea con las prestaciones mínimas de ley (aguinaldo de 15 días, prima vacacional de 25 % y vacaciones según la tabla de ley), de modo que queda lista para nómina de inmediato. La operación es idempotente: en modo lote nunca se duplican. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/legal-entities` ## Solicitud unica — minima [#solicitud-unica--minima] *** `name` es el único campo requerido. `is_active` toma el valor `true` por defecto. ```json { "name": "Acme S.A. de C.V." } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/legal-entities" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme S.A. de C.V." }' ``` **Respuesta** ```json { "legal_entity_number": 7, "name": "Acme S.A. de C.V.", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": null } ``` ## Solicitud unica — inactiva al crear [#solicitud-unica--inactiva-al-crear] *** Puedes crear una razón social inactiva pasando `is_active: false`. Aparecerá en `GET` (salvo que filtres por `active=true`) y podrá activarse después con `PATCH`. ```json { "name": "Acme S.A. de C.V.", "is_active": false } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/legal-entities" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme S.A. de C.V.", "is_active": false }' ``` **Respuesta** ```json { "legal_entity_number": 7, "name": "Acme S.A. de C.V.", "is_active": false, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": null } ``` ## Solicitud por lote (1–100 razones sociales) [#solicitud-por-lote-1100-razones-sociales] *** Envía un **arreglo** de hasta 100 razones sociales en el mismo endpoint. Si **cualquier** fila falla la validación, **ninguna** razón social se crea y la respuesta lista los problemas por fila. ```json [ { "name": "Acme S.A. de C.V." }, { "name": "Beta S.A. de C.V." } ] ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/legal-entities" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "name": "Acme S.A. de C.V." }, { "name": "Beta S.A. de C.V." } ]' ``` **Respuesta** ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "legal_entity_number": 7, "name": "Acme S.A. de C.V.", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": null }, { "legal_entity_number": 8, "name": "Beta S.A. de C.V.", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": null } ] } ``` ## Reglas de los campos [#reglas-de-los-campos] | Campo | Tipo | Requerido | Notas | | ----------- | ------- | --------- | -------------------------------- | | `name` | string | Sí | No vacío tras recortar espacios. | | `is_active` | boolean | No | Por defecto `true`. | ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — solicitud única [#201--solicitud-única] Cuando el cuerpo es **un objeto**, la respuesta es el objeto de la razón social creada. ```json { "legal_entity_number": 7, "name": "Acme S.A. de C.V.", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": null } ``` ### `201` — solicitud por lote [#201--solicitud-por-lote] Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "legal_entity_number": 7, "name": "Acme S.A. de C.V.", "...": "..." }, { "legal_entity_number": 8, "name": "Beta S.A. de C.V.", "...": "..." } ] } ``` ## Errores [#errores] ### `400` — JSON inválido [#400--json-inválido] ```json { "message": "Invalid JSON body." } ``` ### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única] Para fallas de validación de esquema (campo faltante, tipo incorrecto), `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "name": ["Ingresa el nombre."] } } ``` ### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote] Reporte por fila. **Ninguna** razón social del lote fue creada. ```json { "message": "Validation failed for 1 legal entity(ies). No legal entities were created.", "summary": { "total": 2, "with_errors": 1 }, "errors": [ { "index": 0, "legal_entity": { "name": "" }, "issues": [ { "field": "name", "code": "INVALID_INPUT", "message": "Ingresa el nombre." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `legal_entity.name` — extraído de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. Si la falla es a nivel del arreglo (por ejemplo más de 100 elementos), la respuesta cae al formato plano indexado por campo: `{ "message": "Validation failed.", "errors": { ... } }`. ### `500` [#500] ```json { "message": "Internal Server Error" } ``` ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] | Codigo | Significado | | --------------- | ------------------------------------------------------------------------------------------------------ | | `INVALID_INPUT` | Falla genérica de validación de esquema (campo faltante, tipo incorrecto, `name` vacío tras recortar). | # Eliminar (/docs/developers/api-reference/legal-entities/delete) ## Descripcion general [#descripcion-general] Este endpoint realiza el **borrado** de la razón social y la retira de los listados de `GET`/`PATCH`/`DELETE` (las llamadas posteriores responden `404`). La respuesta exitosa es `204 No Content` con cuerpo vacío. **No se puede eliminar mientras tenga empleos vinculados.** Si algún empleo (de cualquier estatus, **incluidos los dados de baja**) referencia esta razón social, el borrado se rechaza con `409` — primero debes reasignar esos empleos. Es intencional: los empleos dados de baja la siguen refiriendo por integridad histórica/SAT. **El número no se reutiliza.** `legal_entity_number` de una razón social eliminada nunca se reasigna a una creación futura. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/legal-entities/{legal_entity_number}` **Path param:** `legal_entity_number` — entero positivo. Cualquier otro valor responde con `400`. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/legal-entities/7" \ -H "Authorization: Bearer TU_API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] * **Rechaza** con `409` si algún empleo referencia esta razón social (cualquier estatus, incluidos los dados de baja) — reasígnalos primero. * Retira la razón social de los listados. * La razón social deja de devolverse por `GET /api/v1/legal-entities`; las llamadas a `PATCH`/`DELETE` posteriores responden `404`. * `legal_entity_number` no se reasigna a creaciones futuras. ## Errores [#errores] | Status | Causa | | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `legal_entity_number` inválido en la URL (no es entero positivo). Cuerpo: `{ "message": "Invalid 'legal_entity_number' in URL: must be a positive integer." }`. | | `404` | La razón social no existe o ya fue eliminada. Cuerpo: `{ "message": "Legal entity not found." }`. | | `409` | La razón social sigue referenciada por uno o más empleos. Cuerpo: `{ "message": "This legal entity is still referenced by one or more employments. Reassign them before deleting it." }`. | | `500` | Error no manejado. Cuerpo: `{ "message": "Internal Server Error" }`. | # Editar (/docs/developers/api-reference/legal-entities/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente una razón social. Todos los campos del cuerpo son **opcionales**: si omites una clave, ese campo no se toca. El identificador en la URL es el `legal_entity_number` dentro de tu organización. Una solicitud exitosa devuelve `200` con la forma completa de la razón social actualizada. Las razones sociales eliminadas se tratan como no encontradas (`404`). `is_active` **no** elimina la razón social: solo la marca como inactiva, y sigue apareciendo en `GET` (salvo que filtres con `active=false`). **Desactivarla (`is_active: false`) se rechaza con `409`** si algún empleo (de cualquier estatus) la referencia — reasigna esos empleos primero. Para hacer un borrado real, usa [DELETE](/docs/developers/api-reference/legal-entities/delete). ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/legal-entities/{legal_entity_number}` **Path param:** `legal_entity_number` — entero positivo. Cualquier otro valor responde con `400`. ## Renombrar [#renombrar] *** Envía solo los campos que quieras cambiar. El resto de la razón social queda intacta. ```json { "name": "Acme Holdings S.A. de C.V." } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/legal-entities/7" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Holdings S.A. de C.V." }' ``` **Respuesta** ```json { "legal_entity_number": 7, "name": "Acme Holdings S.A. de C.V.", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T14:30:00.000Z" } ``` ## Marcar como inactiva [#marcar-como-inactiva] *** Cambiar `is_active` a `false` marca la razón social como inactiva, pero **no** la elimina: sigue apareciendo en `GET /api/v1/legal-entities` (salvo que filtres por `active=true`) y puede reactivarse después. **La desactivación se rechaza con `409`** si algún empleo (cualquier estatus, incluidos los dados de baja) referencia la razón social — primero reasígnalos. Para retirarla por completo, usa [DELETE](/docs/developers/api-reference/legal-entities/delete). ```json { "is_active": false } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/legal-entities/7" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "is_active": false }' ``` **Respuesta** ```json { "legal_entity_number": 7, "name": "Acme Holdings S.A. de C.V.", "is_active": false, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T14:35:00.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | string | No vacío tras recortar cuando se envía. | | `is_active` | boolean | Alternar este flag **no** elimina la razón social: solo la marca como inactiva. Ponerlo en `false` se rechaza con `409` si algún empleo referencia la razón social. Para borrarla, usa `DELETE`. | ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — misma forma que la respuesta de [crear](/docs/developers/api-reference/legal-entities/create) en modo single. ## Errores [#errores] | Status | Causa | | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `legal_entity_number` inválido en la URL (no es entero positivo); cuerpo JSON inválido; falla de validación de esquema. | | `404` | La razón social no existe (o fue eliminada) en esta organización. Cuerpo: `{ "message": "Legal entity not found." }`. | | `409` | Intento de desactivar (`is_active: false`) una razón social referenciada por uno o más empleos. Cuerpo: `{ "message": "This legal entity is still referenced by one or more employments. Reassign them before deactivating it." }`. | | `500` | Error no manejado. Cuerpo: `{ "message": "Internal Server Error" }`. | # Listar (/docs/developers/api-reference/legal-entities/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de las razones sociales pertenecientes a tu organización. Una razón social (legal entity) agrupa empleados y registros patronales bajo un mismo nombre fiscal. Las razones sociales eliminadas se excluyen automáticamente. El tamaño de página es fijo en 20 resultados. `legal_entity_number` es un identificador secuencial por organización, estable y reutilizado como segmento de URL en `PATCH` y `DELETE`. Los números de razones sociales eliminadas **no** se reasignan a registros futuros. `updated_at` es `null` mientras la razón social no haya sido modificada desde su creación. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/legal-entities` ## Consulta multiples razones sociales [#consulta-multiples-razones-sociales] *** ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | --------- | --------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. Valores fuera de rango o no numéricos caen a `1`. El tamaño de página es fijo en 20. | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. Cualquier otro valor responde con `400`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/legal-entities" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 42, "page": 1, "page_size": 20, "total_pages": 3, "next": "https://app.integranom.com/api/v1/legal-entities?page=2", "previous": null, "results": [ { "legal_entity_number": 1, "name": "Acme S.A. de C.V.", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": null } ] } ``` ## Consulta una razon social especifica [#consulta-una-razon-social-especifica] *** Se puede buscar una razón social particular por su número (org-scoped) o por nombre exacto (recortado). Los nombres no son únicos dentro de la organización. ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | --------------------- | ---------------- | --------- | ------- | -------------------------------------------------------------------------------------------- | | `legal_entity_number` | integer positivo | No | - | Filtra por número exacto. Debe ser entero positivo; cualquier otro valor responde con `400`. | | `name` | string | No | - | Filtra por nombre exacto (recortado). | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/legal-entities?legal_entity_number=1" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "legal_entity_number": 1, "name": "Acme S.A. de C.V.", "is_active": true, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": null } ] } ``` ## Forma del recurso [#forma-del-recurso] Cada elemento de `results[]` (y cada respuesta de `POST`/`PATCH` single) tiene la siguiente forma: | Campo | Tipo | Notas | | --------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `legal_entity_number` | integer | Identificador secuencial por organización. Estable, reutilizado como segmento de URL. Los números de razones sociales eliminadas no se reasignan. | | `name` | string | Razón social. No es necesario que sea única dentro de la organización. | | `is_active` | boolean | Controlado por el usuario. Independiente del borrado — una razón social inactiva sigue apareciendo en `GET` (salvo que filtres con `active=false`). | | `created_at` | timestamp ISO 8601 (UTC) | | | `updated_at` | timestamp ISO 8601 (UTC) \| null | `null` mientras la razón social no haya sido modificada desde su creación. | ## Errores [#errores] | Status | Causa | | ------ | --------------------------------------------------------------------------------- | | `400` | `legal_entity_number` no es entero positivo; o `active` no es `"true"`/`"false"`. | | `401` | API key ausente o inválida. | | `403` | API key no vinculada a una organización, o no autorizada para esta operación. | | `500` | Error no manejado. Cuerpo: `{ "message": "Internal Server Error" }`. | # Catálogos (/docs/developers/api-reference/organizational-unit-types/catalogs) ## Descripcion general [#descripcion-general] Los campos `icon` y `color` aceptan únicamente valores de las listas siguientes y se validan en el servidor. Cualquier valor fuera del catálogo devuelve `400` en `POST` y `PATCH`. Los colores son códigos hex de 7 caracteres (`#RRGGBB`), insensibles a mayúsculas en la entrada y normalizados a mayúsculas al guardarse. ## Iconos [#iconos] Identificadores aceptados para el campo `icon`. La columna izquierda es el valor que envías; a la derecha, el icono que le corresponde. | Valor | Descripción | | --- | --- | | `map_pin` | Pin / ubicación | | `world` | Globo / mundo | | `map` | Mapa / región | | `building` | Edificio genérico | | `building_store` | Tienda / sucursal | | `building_factory` | Fábrica / planta | | `building_warehouse` | Almacén | | `building_bank` | Banco | | `briefcase` | Servicio / negocio | | `users` | Equipo / personas | | `hierarchy` | Jerarquía / organigrama | | `category` | Categoría / agrupación | | `chart_bar` | Reportes / métricas | | `layers_intersect` | Estructura en capas | | `tag` | Etiqueta | | `star` | Destacado | ## Colores [#colores] Códigos hex aceptados para el campo `color`. La columna izquierda es el valor que envías (cualquier capitalización); a la izquierda se muestra el tono renderizado. | Valor | Tono | | --- | --- | | `#0EA5E9` | Azul cielo | | `#22C55E` | Verde | | `#F59E0B` | Ámbar | | `#EF4444` | Rojo | | `#A855F7` | Morado | | `#EC4899` | Rosa | | `#14B8A6` | Verde azulado | | `#6366F1` | Índigo | | `#84CC16` | Lima | | `#64748B` | Pizarra | # Crear (/docs/developers/api-reference/organizational-unit-types/create) ## Descripcion general [#descripcion-general] Este endpoint permite crear uno o varios tipos de unidades dentro de tu organización. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. Los campos obligatorios por fila son `name` y `key`; `icon` y `color` son opcionales pero, si se envían, deben pertenecer al [catálogo](/docs/developers/api-reference/organizational-unit-types/catalogs). Una solicitud exitosa devuelve `201` con los tipos creados, incluyendo los campos generados por el servidor (`organizational_unit_type_number`, `created_at`, `updated_at`). ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/organizational-unit-types` ## Solicitud unica [#solicitud-unica] *** Envía un objeto con `name` y `key`, opcionalmente `icon` y `color`. Si omites `icon` o `color`, el tipo se crea sin esos valores. ```json { "name": "Sucursal", "key": "branch", "color": "#0EA5E9", "icon": "building_store" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/organizational-unit-types" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sucursal", "key": "branch", "color": "#0EA5E9", "icon": "building_store" }' ``` **Respuesta** ```json { "organizational_unit_type_number": 5, "name": "Sucursal", "key": "branch", "color": "#0EA5E9", "icon": "building_store", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ## Solicitud por lote (1–100 tipos) [#solicitud-por-lote-1100-tipos] *** Envía un **arreglo** de hasta 100 tipos en el mismo endpoint; el modo lote se activa al enviar un arreglo como cuerpo. Si **cualquier** fila falla la validación, **ningún** tipo se crea y la respuesta lista los problemas por fila. ```json [ { "name": "Región", "key": "region", "icon": "map", "color": "#6366F1" }, { "name": "Planta", "key": "plant", "icon": "building_factory", "color": "#22C55E" }, { "name": "Almacén", "key": "warehouse", "icon": "building_warehouse" } ] ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/organizational-unit-types" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "name": "Región", "key": "region", "icon": "map", "color": "#6366F1" }, { "name": "Planta", "key": "plant", "icon": "building_factory", "color": "#22C55E" }, { "name": "Almacén", "key": "warehouse", "icon": "building_warehouse" } ]' ``` **Respuesta** ```json { "summary": { "total": 3, "created": 3 }, "created": [ { "organizational_unit_type_number": 6, "name": "Región", "key": "region", "color": "#6366F1", "icon": "map", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" }, { "organizational_unit_type_number": 7, "name": "Planta", "key": "plant", "color": "#22C55E", "icon": "building_factory", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" }, { "organizational_unit_type_number": 8, "name": "Almacén", "key": "warehouse", "color": null, "icon": "building_warehouse", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Reglas de los campos [#reglas-de-los-campos] | Campo | Tipo | Requerido | Notas | | ------------ | ----------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Sí | No vacío tras recortar espacios. | | `key` | string | Sí | Identificador estable de máquina. Debe coincidir con `^[a-z][a-z0-9_]*$` (inicia con minúscula, seguida de minúsculas/dígitos/guiones bajos). Máx. 60 caracteres. Único por organización. **Inmutable** una vez creado — `PATCH` no lo acepta. | | `color` | string | No | Hex del [catálogo de colores](/docs/developers/api-reference/organizational-unit-types/catalogs). Insensible a mayúsculas en la entrada; se guarda en mayúsculas. | | `icon` | string | No | Identificador `snake_case` del [catálogo de iconos](/docs/developers/api-reference/organizational-unit-types/catalogs). | | `is_active` | boolean | No | Por defecto `true`. Independiente del borrado — usa `DELETE` para borrar. | | `sort_order` | integer ≥ 0 | No | Por defecto `0`. | ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — solicitud única [#201--solicitud-única] Cuando el cuerpo es **un objeto**, la respuesta es el objeto del tipo creado. ```json { "organizational_unit_type_number": 5, "name": "Sucursal", "key": "branch", "color": "#0EA5E9", "icon": "building_store", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ### `201` — solicitud por lote [#201--solicitud-por-lote] Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "organizational_unit_type_number": 6, "name": "Región", "...": "..." }, { "organizational_unit_type_number": 7, "name": "Planta", "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única] Para fallas de validación de campos, `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "key": ["Usa solo minúsculas, números y guiones bajos. Debe iniciar con letra."] } } ``` ### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote] Reporte por fila. **Ningún** tipo del lote fue creado. ```json { "message": "Validation failed for 2 organizational unit type(s). No types were created.", "errors": [ { "index": 0, "organizational_unit_type": { "name": "Sucursal", "key": "branch" }, "issues": [ { "field": "key", "code": "DUPLICATE_KEY", "message": "An organizational unit type with key 'branch' already exists in this organization." } ] }, { "index": 2, "organizational_unit_type": { "name": "Sucursal CDMX", "key": "branch" }, "issues": [ { "field": "key", "code": "DUPLICATE_KEY_IN_BATCH", "message": "Key 'branch' appears multiple times in this batch." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `organizational_unit_type.name` / `organizational_unit_type.key` — extraídos de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. ### `409` — condición de carrera [#409--condición-de-carrera] Se devuelve cuando otra solicitud reclama un `key` entre el momento en que validamos el tuyo y el momento en que escribimos. En modo lote, la transacción completa hace rollback — ningún tipo es creado. Reintenta la misma solicitud. ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] | Codigo | Significado | | ------------------------- | ------------------------------------------------------------------------------------------------- | | `INVALID_INPUT` | Falla genérica de validación de campos (icon o color fuera del catálogo, `key` malformado, etc.). | | `DUPLICATE_KEY` | El `key` ya existe en la organización. | | `DUPLICATE_KEY_IN_BATCH` | Dos o más filas del lote envían el mismo `key`. | | `CONCURRENT_MODIFICATION` | Señal de reintento por condición de carrera (respuesta `409`). | # Eliminar (/docs/developers/api-reference/organizational-unit-types/delete) ## Descripcion general [#descripcion-general] Este endpoint realiza el **borrado** del tipo de unidad: a partir de ese momento deja de aparecer en `GET /api/v1/organizational-unit-types` y se trata como no encontrada en `PATCH`/`DELETE` (`404`). El borrado **rechaza** si el tipo tiene unidades organizacionales vivas referenciándolo — primero debes reasignarlas o eliminarlas. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/organizational-unit-types/{organizational_unit_type_number}` **Path param:** `organizational_unit_type_number` — entero positivo, el número del tipo dentro de la organización. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/organizational-unit-types/5" \ -H "Authorization: Bearer TU_API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] 1. **Rechaza** la solicitud (`400 HAS_UNITS`) si el tipo tiene **unidades organizacionales vivas** referenciándolo — primero reasígnalas o elimínalas. El cuerpo de error incluye un conteo: `"N live organizational unit(s) reference this type."`. 2. **Marca** el tipo como eliminado. Deja de aparecer en `GET /api/v1/organizational-unit-types`. ## Errores [#errores] | Status | Codigo | Causa | | ------ | ----------- | ----------------------------------------------------------------------------- | | `400` | — | `organizational_unit_type_number` inválido en la URL (no es entero positivo). | | `400` | `HAS_UNITS` | Una o más unidades organizacionales vivas referencian este tipo. | | `404` | — | El tipo no existe o ya fue eliminado en esta organización. | # Editar (/docs/developers/api-reference/organizational-unit-types/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente un tipo de unidad. Todos los campos del cuerpo son **opcionales**: si omites una clave, ese campo no se toca; si la envías como `null`, ese campo se limpia (cuando aplica). El identificador en la URL es el `organizational_unit_type_number` dentro de tu organización. Una solicitud exitosa devuelve `200` con la forma completa del tipo actualizado. **`key` no es editable.** El `key` es el identificador estable referenciado por exportaciones e integraciones; cambiarlo después de la creación dejaría esas referencias huérfanas. Para "renombrar" un `key`, crea un nuevo tipo con el `key` deseado, reasigna las unidades, y elimina el tipo viejo. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/organizational-unit-types/{organizational_unit_type_number}` **Path param:** `organizational_unit_type_number` — entero positivo, el número del tipo dentro de la organización. ## Renombrar y recolorear [#renombrar-y-recolorear] *** Envía solo los campos que quieras cambiar. El resto del tipo queda intacto. ```json { "name": "Sucursal comercial", "color": "#F59E0B" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/organizational-unit-types/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sucursal comercial", "color": "#F59E0B" }' ``` **Respuesta** ```json { "organizational_unit_type_number": 5, "name": "Sucursal comercial", "key": "branch", "color": "#F59E0B", "icon": "building_store", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:15:32.000Z" } ``` ## Limpiar icon o color [#limpiar-icon-o-color] *** `null` limpia el campo. Aplica a `icon` y `color`; el resto de campos no se limpian con `null`. ```json { "icon": null } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/organizational-unit-types/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "icon": null }' ``` **Respuesta** ```json { "organizational_unit_type_number": 5, "name": "Sucursal comercial", "key": "branch", "color": "#F59E0B", "icon": null, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:18:09.000Z" } ``` ## Desactivar sin borrar [#desactivar-sin-borrar] *** `is_active: false` marca el tipo como inactivo, pero lo deja visible en `GET`. Es independiente del borrado — para borrarlo realmente usa [DELETE](/docs/developers/api-reference/organizational-unit-types/delete). **La desactivación se rechaza con `409`** si el tipo tiene unidades organizacionales vivas vinculadas — primero reasígnalas o elimínalas. ```json { "is_active": false } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/organizational-unit-types/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "is_active": false }' ``` **Respuesta** ```json { "organizational_unit_type_number": 5, "name": "Sucursal comercial", "key": "branch", "color": "#F59E0B", "icon": null, "is_active": false, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:22:44.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | ------------ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | No vacío tras recortar. | | `color` | string \| null | Hex del [catálogo](/docs/developers/api-reference/organizational-unit-types/catalogs). `null` limpia el color. | | `icon` | string \| null | `snake_case` del [catálogo](/docs/developers/api-reference/organizational-unit-types/catalogs). `null` limpia el icono. | | `is_active` | boolean | Flag activo controlado por el usuario. Independiente del borrado — un tipo puede estar inactivo y seguir apareciendo en `GET`. Ponerlo en `false` se rechaza con `409` si hay unidades vivas vinculadas al tipo. | | `sort_order` | integer ≥ 0 | | **No se acepta `key`.** Ver el aviso al inicio de la página. ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — misma forma que la respuesta de [crear](/docs/developers/api-reference/organizational-unit-types/create) en modo single: ```json { "organizational_unit_type_number": 5, "name": "Sucursal", "key": "branch", "color": "#0EA5E9", "icon": "building_store", "is_active": true, "sort_order": 0, "created_at": "...", "updated_at": "..." } ``` ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `400` | `organizational_unit_type_number` inválido en la URL; falla de validación del cuerpo; `icon` o `color` fuera del catálogo. | | `404` | El tipo con ese `organizational_unit_type_number` no existe (o fue eliminado) en esta organización. | | `409` | Intento de desactivar (`is_active: false`) un tipo con unidades organizacionales vivas vinculadas. Cuerpo: `{ "message": "This unit type still has organizational units linked to it. Reassign or delete them before deactivating it." }`. | # Listar (/docs/developers/api-reference/organizational-unit-types/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de los tipos de unidades organizacionales pertenecientes a tu organización. Cada tipo incluye su `key` (identificador estable), `icon` y `color` del [catálogo](/docs/developers/api-reference/organizational-unit-types/catalogs). Los tipos eliminados se excluyen automáticamente. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/organizational-unit-types` ## Consulta multiples tipos [#consulta-multiples-tipos] *** ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | --------- | --------------------- | --------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/organizational-unit-types" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 4, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "organizational_unit_type_number": 1, "name": "Sucursal", "key": "branch", "color": "#0EA5E9", "icon": "building_store", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Consulta un tipo especifico [#consulta-un-tipo-especifico] *** Se puede buscar un tipo particular por su número, nombre o `key`. Los tres filtros hacen coincidencia exacta; `key` distingue mayúsculas y minúsculas (siempre `snake_case` en minúsculas). ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | --------------------------------- | ------- | --------- | ------- | ------------------------------------------------ | | `organizational_unit_type_number` | integer | No | - | Filtra por número de tipo exacto. | | `name` | string | No | - | Filtra por nombre exacto. | | `key` | string | No | - | Filtra por `key` exacto (sensible a mayúsculas). | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/organizational-unit-types?key=branch" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "organizational_unit_type_number": 1, "name": "Sucursal", "key": "branch", "color": "#0EA5E9", "icon": "building_store", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | --------------------------------------------------------------- | | `400` | Valor inválido en `organizational_unit_type_number` o `active`. | | `401` | API key ausente o inválida. | # Crear (/docs/developers/api-reference/organizational-units/create) ## Descripcion general [#descripcion-general] Este endpoint permite crear una o varias unidades organizacionales dentro de tu organización. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. Cada unidad pertenece a un tipo (`organizational_unit_type_key`) y puede anidarse bajo otra unidad **del mismo tipo** mediante `parent_organizational_unit_number`. Una solicitud exitosa devuelve `201` con las unidades creadas, incluyendo los campos generados por el servidor (`organizational_unit_number`, `depth`, `created_at`, `updated_at`). En modo lote, si **cualquier** fila falla la validación, **ninguna** unidad se crea — la respuesta lista los problemas por fila para corregirlos en una sola pasada. **Referencias entre filas no permitidas.** `parent_organizational_unit_number` debe resolverse contra una unidad que **ya exista** en la base de datos. No se pueden crear referencias entre filas del mismo lote — para construir árboles multi-nivel, envía lotes secuenciales (raíces primero, luego hijos). **Regla del padre del mismo tipo.** Una unidad solo puede anidarse bajo un padre con el **mismo** `organizational_unit_type_key`. Mezclar tipos en una misma jerarquía (por ejemplo una sucursal bajo una región) se rechaza con `400 PARENT_TYPE_MISMATCH`. Cada tipo gestiona su propia jerarquía como un árbol independiente. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/organizational-units` ## Solicitud unica — unidad raiz [#solicitud-unica--unidad-raiz] *** Envía `name` y `organizational_unit_type_key`. Sin `parent_organizational_unit_number` la unidad se crea como raíz del árbol de ese tipo (`depth: 0`). ```json { "name": "Norte", "organizational_unit_type_key": "region", "code": "REG-N" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/organizational-units" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Norte", "organizational_unit_type_key": "region", "code": "REG-N" }' ``` **Respuesta** ```json { "organizational_unit_number": 1, "organizational_unit_type_key": "region", "parent_organizational_unit_number": null, "depth": 0, "name": "Norte", "code": "REG-N", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ## Solicitud unica — anidada bajo un padre [#solicitud-unica--anidada-bajo-un-padre] *** Para anidar una unidad bajo otra existente, envía `parent_organizational_unit_number`. La API calcula `depth` y verifica que el padre exista y comparta el mismo `organizational_unit_type_key`. ```json { "name": "Monterrey", "organizational_unit_type_key": "region", "code": "REG-MTY", "parent_organizational_unit_number": 1 } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/organizational-units" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Monterrey", "organizational_unit_type_key": "region", "code": "REG-MTY", "parent_organizational_unit_number": 1 }' ``` **Respuesta** ```json { "organizational_unit_number": 5, "organizational_unit_type_key": "region", "parent_organizational_unit_number": 1, "depth": 1, "name": "Monterrey", "code": "REG-MTY", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ## Solicitud por lote (1–100 unidades) [#solicitud-por-lote-1100-unidades] *** Envía un **arreglo** de hasta 100 unidades en el mismo endpoint. Si **cualquier** fila falla la validación, **ninguna** unidad se crea y la respuesta lista los problemas por fila. ```json [ { "name": "Monterrey", "organizational_unit_type_key": "region", "code": "REG-MTY", "parent_organizational_unit_number": 1 }, { "name": "Guadalajara", "organizational_unit_type_key": "region", "code": "REG-GDL", "parent_organizational_unit_number": 1 }, { "name": "Tijuana", "organizational_unit_type_key": "region", "code": "REG-TIJ", "parent_organizational_unit_number": 1 } ] ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/organizational-units" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "name": "Monterrey", "organizational_unit_type_key": "region", "code": "REG-MTY", "parent_organizational_unit_number": 1 }, { "name": "Guadalajara", "organizational_unit_type_key": "region", "code": "REG-GDL", "parent_organizational_unit_number": 1 }, { "name": "Tijuana", "organizational_unit_type_key": "region", "code": "REG-TIJ", "parent_organizational_unit_number": 1 } ]' ``` **Respuesta** ```json { "summary": { "total": 3, "created": 3 }, "created": [ { "organizational_unit_number": 6, "organizational_unit_type_key": "region", "parent_organizational_unit_number": 1, "depth": 1, "name": "Monterrey", "code": "REG-MTY", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" }, { "organizational_unit_number": 7, "organizational_unit_type_key": "region", "parent_organizational_unit_number": 1, "depth": 1, "name": "Guadalajara", "code": "REG-GDL", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" }, { "organizational_unit_number": 8, "organizational_unit_type_key": "region", "parent_organizational_unit_number": 1, "depth": 1, "name": "Tijuana", "code": "REG-TIJ", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Reglas de los campos [#reglas-de-los-campos] | Campo | Tipo | Requerido | Notas | | ----------------------------------- | ---------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Sí | No vacío tras recortar espacios. | | `organizational_unit_type_key` | string | Sí | `key` del tipo de unidad (snake\_case). Debe corresponder a un tipo activo de la organización. | | `code` | string | No | ≤ 60 caracteres, se almacena en mayúsculas. | | `parent_organizational_unit_number` | integer positivo | No | Número de una unidad existente para anidar. **Debe ser del mismo tipo** que esta fila. No se permiten referencias dentro del lote. | | `is_active` | boolean | No | Por defecto `true`. | | `sort_order` | integer ≥ 0 | No | Por defecto `0`. | ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — solicitud única [#201--solicitud-única] Cuando el cuerpo es **un objeto**, la respuesta es el objeto de la unidad creada. ```json { "organizational_unit_number": 5, "organizational_unit_type_key": "region", "parent_organizational_unit_number": 1, "depth": 1, "name": "Monterrey", "code": "REG-MTY", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ### `201` — solicitud por lote [#201--solicitud-por-lote] Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 3, "created": 3 }, "created": [ { "organizational_unit_number": 6, "name": "Monterrey", "...": "..." }, { "organizational_unit_number": 7, "name": "Guadalajara", "...": "..." }, { "organizational_unit_number": 8, "name": "Tijuana", "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única] ```json { "message": "Validation failed.", "errors": [ { "field": "organizational_unit_type_key", "code": "TYPE_NOT_FOUND", "message": "No organizational unit type with key 'region' in this organization." } ] } ``` Para fallas de validación de esquema (campo obligatorio faltante, tipo incorrecto), `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "name": ["Ingresa el nombre."] } } ``` ### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote] Reporte por fila. **Ninguna** unidad del lote fue creada. ```json { "message": "Validation failed for 2 organizational unit(s). No units were created.", "errors": [ { "index": 0, "organizational_unit": { "name": "Monterrey", "organizational_unit_type_key": "region" }, "issues": [ { "field": "parent_organizational_unit_number", "code": "PARENT_NOT_FOUND", "message": "No organizational unit with number '99' in this organization." } ] }, { "index": 2, "organizational_unit": { "name": "Sucursal CDMX", "organizational_unit_type_key": "branch" }, "issues": [ { "field": "parent_organizational_unit_number", "code": "PARENT_TYPE_MISMATCH", "message": "The parent organizational unit's type must match this unit's type." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `organizational_unit.name` / `organizational_unit.organizational_unit_type_key` — extraídos de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos: | Codigo | Significado | | ---------------------- | ---------------------------------------------------------------------------------- | | `INVALID_INPUT` | Falla genérica de validación de esquema (campo faltante, `key` mal formada, etc.). | | `TYPE_NOT_FOUND` | `organizational_unit_type_key` no coincide con un tipo activo de la organización. | | `PARENT_NOT_FOUND` | `parent_organizational_unit_number` no coincide con una unidad viva. | | `PARENT_TYPE_MISMATCH` | El tipo del padre no coincide con el tipo de esta unidad. | # Eliminar (/docs/developers/api-reference/organizational-units/delete) ## Descripcion general [#descripcion-general] Este endpoint realiza el **borrado** de la unidad: a partir de ese momento deja de aparecer en `GET /api/v1/organizational-units` y se trata como no encontrada en `PATCH`/`DELETE` (`404`). La respuesta exitosa es `204 No Content` con cuerpo vacío. **Bloqueo por empleados asignados.** Si algún empleado del subárbol sigue asignado a esta unidad (asignación abierta, `removedAt = null`), el borrado se rechaza con `409 IN_USE` — primero reasigna esas asignaciones. Es intencional: una asignación abierta bloquea incluso en un empleo dado de baja, por integridad histórica/SAT. **No-recursivo: hijos vivos bloquean el borrado.** Si la unidad tiene unidades hija vivas (es decir, alguna unidad cuya `parent_organizational_unit_number` sea esta), la solicitud falla con `400 HAS_CHILDREN`. Primero debes eliminarlas o reasignarlas a otro padre. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/organizational-units/{organizational_unit_number}` **Path param:** `organizational_unit_number` — entero positivo, el número de la unidad dentro de la organización. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/organizational-units/5" \ -H "Authorization: Bearer TU_API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] * Rechaza con `400 HAS_CHILDREN` si la unidad tiene unidades hija vivas; el cuerpo incluye un conteo: `"N live organizational unit(s) reference this one as parent."` Primero hay que eliminar o reasignar los hijos. * Rechaza con `409 IN_USE` si algún empleado del subárbol sigue asignado (asignación abierta, `removedAt = null`) — reasígnalos primero. * En caso contrario marca la unidad como eliminada. * La fila deja de devolverse por `GET /api/v1/organizational-units`. ## Errores [#errores] | Status | Código | Causa | | ------ | -------------- | ------------------------------------------------------------------------ | | `400` | — | `organizational_unit_number` inválido en la URL (no es entero positivo). | | `400` | `HAS_CHILDREN` | Una o más unidades hija vivas referencian a esta unidad como padre. | | `409` | `IN_USE` | Uno o más empleados siguen asignados dentro del subárbol de esta unidad. | | `404` | — | La unidad no existe o ya fue eliminada en esta organización. | # Editar (/docs/developers/api-reference/organizational-units/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente una unidad organizacional. Todos los campos del cuerpo son **opcionales**: si omites una clave, ese campo no se toca; si la envías como `null`, ese campo se limpia (cuando aplica). El identificador en la URL es el `organizational_unit_number` de la unidad dentro de tu organización. Una solicitud exitosa devuelve `200` con la forma completa de la unidad actualizada. Las unidades eliminadas se tratan como no encontradas (`404`). **Cambiar `organizational_unit_type_key` desencadena un cascade masivo.** Cambiar el tipo de una unidad reescribe el `typeId` de **todos los descendientes** del subárbol y también el `unitTypeId` denormalizado de **todos los empleados** vinculados, todo dentro de la misma transacción. Un solo `PATCH` sobre una raíz con 1000 descendientes y 5000 empleados vinculados toca a todos. Úsalo con moderación. **Reparenting (cambiar `parent_organizational_unit_number`).** El nuevo padre debe ser del **mismo tipo** efectivo que la unidad (sino `PARENT_TYPE_MISMATCH`) y no puede ser la propia unidad ni un descendiente (sino `CYCLE_DETECTED`). Se recalculan `path` y `depth` de la unidad y de **todos los descendientes** en la misma transacción. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/organizational-units/{organizational_unit_number}` **Path param:** `organizational_unit_number` — entero positivo, el número de la unidad dentro de la organización. ## Renombrar [#renombrar] *** Envía solo los campos que quieras cambiar. El resto de la unidad queda intacto. ```json { "name": "Monterrey Norte" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/organizational-units/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Monterrey Norte" }' ``` **Respuesta** ```json { "organizational_unit_number": 5, "organizational_unit_type_key": "region", "parent_organizational_unit_number": 1, "depth": 1, "name": "Monterrey Norte", "code": "REG-MTY", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:15:32.000Z" } ``` ## Convertir en raiz [#convertir-en-raiz] *** Envía `parent_organizational_unit_number: null` para desanclar la unidad del árbol. Queda con `depth: 0` y los `path`/`depth` de sus descendientes se recalculan en la misma transacción. ```json { "parent_organizational_unit_number": null } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/organizational-units/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "parent_organizational_unit_number": null }' ``` **Respuesta** ```json { "organizational_unit_number": 5, "organizational_unit_type_key": "region", "parent_organizational_unit_number": null, "depth": 0, "name": "Monterrey Norte", "code": "REG-MTY", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:20:00.000Z" } ``` ## Cambiar de tipo (cascade) [#cambiar-de-tipo-cascade] *** Cambiar `organizational_unit_type_key` reescribe el tipo en todos los descendientes del subárbol y en todos los empleados vinculados a esas unidades, dentro de la misma transacción. Es una operación masiva — úsala con moderación. ```json { "organizational_unit_type_key": "zone" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/organizational-units/5" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "organizational_unit_type_key": "zone" }' ``` **Respuesta** ```json { "organizational_unit_number": 5, "organizational_unit_type_key": "zone", "parent_organizational_unit_number": null, "depth": 0, "name": "Monterrey Norte", "code": "REG-MTY", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:25:00.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | ----------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | No vacío tras recortar. | | `code` | string \| null | `null` o `""` limpian el código. Se almacena en mayúsculas. | | `organizational_unit_type_key` | string | Editable, pero **cascade masivo**: reescribe el `typeId` de cada descendiente y el `unitTypeId` denormalizado de cada empleado vinculado en la misma transacción. | | `parent_organizational_unit_number` | integer positivo \| null | `null` convierte la unidad en raíz. El nuevo padre debe ser del **mismo tipo** que la unidad y no puede ser la propia unidad ni un descendiente. | | `is_active` | boolean | Flag activo controlado por el usuario. Independiente del borrado. Ponerlo en `false` se rechaza con `409` si la unidad tiene hijos vivos (`HAS_CHILDREN`) o si algún empleado del subárbol sigue asignado (`IN_USE`). | | `sort_order` | integer ≥ 0 | | ### Reparenting [#reparenting] Si `parent_organizational_unit_number` cambia: * **Verificación de tipo**: el padre debe compartir el mismo `organizational_unit_type_key` efectivo (sino `400 PARENT_TYPE_MISMATCH`). * **Verificación de ciclos**: el nuevo padre no puede ser la propia unidad ni un descendiente suyo (sino `400 CYCLE_DETECTED`). * Se recalculan `path` y `depth` de la unidad y de **todos los descendientes** del subárbol en la misma transacción. ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — misma forma que la respuesta de [crear](/docs/developers/api-reference/organizational-units/create) en modo single. ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `400` | `organizational_unit_number` inválido en la URL; falla de validación del cuerpo; `TYPE_NOT_FOUND`; `PARENT_NOT_FOUND`; `PARENT_TYPE_MISMATCH`; intento de poner el padre en sí mismo o en un descendiente (`CYCLE_DETECTED`). | | `404` | La unidad con ese `organizational_unit_number` no existe (o fue eliminada) en esta organización. | | `409` | Intento de desactivar (`is_active: false`) una unidad con hijos vivos (`{ "message": "Cannot deactivate an organizational unit with live children. Deactivate or reparent them first." }`) o con empleados asignados en el subárbol (`{ "message": "This organizational unit (or one in its subtree) still has assigned employees. Reassign them before deactivating it." }`). | # Listar (/docs/developers/api-reference/organizational-units/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de las unidades organizacionales pertenecientes a tu organización. Las unidades forman un árbol **por tipo** (cada tipo de unidad gestiona su propia jerarquía) y se identifican con `organizational_unit_number` dentro de la organización. Los resultados incluyen el tipo (`organizational_unit_type_key`), el padre (`parent_organizational_unit_number`) y la profundidad (`depth`). Las unidades eliminadas se excluyen automáticamente. El tamaño de página es fijo en 20 resultados. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/organizational-units` ## Consulta multiples unidades [#consulta-multiples-unidades] *** ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | ------------------------------ | --------------------- | --------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `organizational_unit_type_key` | string | No | - | Restringe los resultados a un tipo de unidad. | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/organizational-units" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 8, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "organizational_unit_number": 1, "organizational_unit_type_key": "region", "parent_organizational_unit_number": null, "depth": 0, "name": "Norte", "code": "REG-N", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" }, { "organizational_unit_number": 2, "organizational_unit_type_key": "region", "parent_organizational_unit_number": 1, "depth": 1, "name": "Monterrey", "code": "REG-MTY", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Consulta una unidad especifica [#consulta-una-unidad-especifica] *** Se puede buscar una unidad particular por su número org-scoped, por nombre exacto o por código exacto. El nombre y el código se almacenan en mayúsculas y el filtro se compara también en mayúsculas, así que no distingue mayúsculas y minúsculas. ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | ---------------------------- | ------- | --------- | ------- | ---------------------------------------------------- | | `organizational_unit_number` | integer | No | - | Filtra por número de unidad exacto. | | `name` | string | No | - | Filtra por nombre exacto. | | `code` | string | No | - | Filtra por código exacto (almacenado en mayúsculas). | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/organizational-units?organizational_unit_number=2" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "organizational_unit_number": 2, "organizational_unit_type_key": "region", "parent_organizational_unit_number": 1, "depth": 1, "name": "Monterrey", "code": "REG-MTY", "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | ---------------------------------------------------------- | | `400` | Valor inválido en `organizational_unit_number` o `active`. | | `401` | API key ausente o inválida. | # Crear (/docs/developers/api-reference/payroll-calendars/create) ## Descripcion general [#descripcion-general] Este endpoint crea calendarios de nómina dentro de tu organización. Cada objeto de creación lleva un arreglo `legal_entity_numbers` y **crea un calendario por cada razón social** de ese arreglo, cada uno con sus propios periodos generados automáticamente a partir de `frequency` y `year`. Por este comportamiento de fan-out, y a diferencia de otros endpoints, la respuesta **siempre** usa la envoltura `{ summary, created }` (nunca un objeto suelto), incluso cuando envías un solo objeto. Acepta **un objeto** o un **arreglo de objetos**; el total de calendarios resultantes —la suma de las longitudes de todos los `legal_entity_numbers`— no puede exceder 100. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/payroll-calendars` ## Solicitud unica — fan-out por razon social [#solicitud-unica--fan-out-por-razon-social] *** Envía un objeto con `legal_entity_numbers`. El ejemplo crea **dos** calendarios idénticos —uno para la razón social `1` y otro para la `2`—, cada uno con sus propios periodos. Aunque envíes un solo objeto, la respuesta siempre es la envoltura `{ summary, created }`. `summary.total` es el número de calendarios que se intentaron crear (la suma de las longitudes de `legal_entity_numbers`) y `summary.created` cuántos se crearon. ```json { "legal_entity_numbers": [1, 2], "name": "Quincenal 2026", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "year": 2026, "pay_day_offset": 3 } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/payroll-calendars" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "legal_entity_numbers": [1, 2], "name": "Quincenal 2026", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "year": 2026, "pay_day_offset": 3 }' ``` **Respuesta** ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "payroll_calendar_number": 3, "legal_entity_number": 1, "name": "Quincenal 2026", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "applies_seventh_day_discount": true, "month_day_factor": 30, "anchor_date": "2026-01-01T00:00:00.000Z", "pay_day_offset": 3, "timezone": "America/Mexico_City", "is_active": true, "periods_count": 24, "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" }, { "payroll_calendar_number": 4, "legal_entity_number": 2, "name": "Quincenal 2026", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "applies_seventh_day_discount": true, "month_day_factor": 30, "anchor_date": "2026-01-01T00:00:00.000Z", "pay_day_offset": 3, "timezone": "America/Mexico_City", "is_active": true, "periods_count": 24, "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" } ] } ``` ## Solicitud por lote [#solicitud-por-lote] *** Envía un **arreglo** de objetos de creación. El fan-out se aplica a cada objeto por separado: el total de calendarios resultantes es la suma de las longitudes de todos los `legal_entity_numbers` y no puede exceder 100. Si **cualquier** fila falla la validación, **ningún** calendario se crea y la respuesta lista los problemas por fila. ```json [ { "legal_entity_numbers": [1], "name": "Quincenal 2026", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "year": 2026, "pay_day_offset": 3 }, { "legal_entity_numbers": [1], "name": "Semanal 2026", "frequency": "weekly", "isr_tariff_frequency": "weekly", "year": 2026, "pay_day_offset": 5 } ] ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/payroll-calendars" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "legal_entity_numbers": [1], "name": "Quincenal 2026", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "year": 2026, "pay_day_offset": 3 }, { "legal_entity_numbers": [1], "name": "Semanal 2026", "frequency": "weekly", "isr_tariff_frequency": "weekly", "year": 2026, "pay_day_offset": 5 } ]' ``` **Respuesta** ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "payroll_calendar_number": 3, "legal_entity_number": 1, "name": "Quincenal 2026", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "applies_seventh_day_discount": true, "month_day_factor": 30, "anchor_date": "2026-01-01T00:00:00.000Z", "pay_day_offset": 3, "timezone": "America/Mexico_City", "is_active": true, "periods_count": 24, "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" }, { "payroll_calendar_number": 4, "legal_entity_number": 1, "...": "..." } ] } ``` ## Reglas de los campos [#reglas-de-los-campos] | Campo | Tipo | Requerido | Notas | | ------------------------------ | ---------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `legal_entity_numbers` | arreglo de enteros positivos | Sí | Al menos un elemento (`≥ 1`). Una razón social por elemento; se crea un calendario para cada una. | | `name` | string | Sí | 3–60 caracteres. Único por razón social. | | `frequency` | string | Sí | Uno de `weekly` \| `biweekly` \| `semimonthly` \| `monthly`. **Inmutable** tras crear. | | `isr_tariff_frequency` | string | No | Tabla del SAT con la que se retiene el ISR. Si se omite, usa `monthly`. Sólo se admiten ese valor (cada periodo se calcula solo) o `monthly` (se acumula el mes y se descuenta lo ya retenido). Cualquier otro valor se rechaza. | | `applies_seventh_day_discount` | boolean | No | Si las faltas y ausencias sin goce descuentan además un sexto del día de descanso (séptimo día proporcional). Si se omite, usa `true`. Un valor que no sea booleano se rechaza con `400`. | | `month_day_factor` | number | No | Días del mes del calendario: `30` o `30.4` (el número, no texto). Con ellos se convierte a diario el sueldo mensual, se fijan los días que paga una nómina mensual y se proyecta el mes en la tarifa mensual del ISR. Si se omite, cada calendario toma el valor por defecto de su razón social (el mes del catálogo `factor_dias_mes`, o `30` si no es 30 ni 30.4). Cualquier otro valor se rechaza con `400` y el mensaje `Los días del mes deben ser 30 o 30.4.` | | `year` | integer | Sí | Entre `2026` y `2100`. Año fiscal usado para generar los periodos; los parámetros fiscales de la plataforma empiezan en `2026`. | | `pay_day_offset` | integer | Sí | Entre `-15` y `15`. Días desde el fin del periodo hasta la fecha de pago; un valor negativo paga antes del cierre del periodo. Cada periodicidad tiene un mínimo para que el pago nunca caiga antes del inicio del periodo: `0` en `daily`, `-6` en `weekly`, `-7` en `decennial`, `-13` en `biweekly`, `-12` en `semimonthly` y `-15` en `monthly`. Un valor menor se rechaza con `400`. | | `first_period_start_day` | string | No | Fecha `AAAA-MM-DD` en que inicia el primer periodo. Solo para `weekly` y `biweekly`, entre el 1 de diciembre del año anterior a `year` y el 31 de enero de `year`. Si se omite, el primer periodo inicia el 1 de enero. Con otra periodicidad, o fuera de ese rango, se rechaza con `400`. El `anchor_date` de la respuesta es el inicio del primer periodo. | | `timezone` | string | No | Por defecto `America/Mexico_City`. **Inmutable** tras crear. | ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — siempre la envoltura [#201--siempre-la-envoltura] Sea el cuerpo **un objeto** o **un arreglo**, la respuesta siempre envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 2, "created": 2 }, "created": [ { "payroll_calendar_number": 3, "legal_entity_number": 1, "...": "..." }, { "payroll_calendar_number": 4, "legal_entity_number": 2, "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida (objeto único) [#400--validación-fallida-objeto-único] Para fallas de validación de campos, `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "name": ["El nombre debe tener entre 3 y 60 caracteres."] } } ``` ### `400` — validación fallida (lote) [#400--validación-fallida-lote] Reporte por fila. **Ningún** calendario del lote fue creado. ```json { "message": "Validation failed.", "summary": { "total": 2, "with_errors": 1 }, "errors": [ { "index": 1, "payroll_calendar": { "name": "Semanal 2026" }, "issues": [ { "field": "legal_entity_numbers", "code": "LEGAL_ENTITY_NOT_FOUND", "message": "Legal entity '99' does not exist in the organization." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `payroll_calendar.name` — extraído de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. ### `400` — total excede el máximo [#400--total-excede-el-máximo] Se devuelve cuando la suma de las longitudes de todos los `legal_entity_numbers` supera 100. ### `409` — nombre duplicado [#409--nombre-duplicado] Se devuelve cuando el `name` ya existe para alguna de las razones sociales dadas. ```json { "message": "A payroll calendar with this 'name' already exists for one of the given legal entities." } ``` ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos: | Codigo | Significado | | ------------------------ | ------------------------------------------------------------------------ | | `INVALID_INPUT` | Falla genérica de validación de campos (formato, longitud, tipo, rango). | | `LEGAL_ENTITY_NOT_FOUND` | Un `legal_entity_number` no existe en la organización. | # Eliminar (/docs/developers/api-reference/payroll-calendars/delete) ## Descripcion general [#descripcion-general] Este endpoint realiza el **borrado** del calendario de nómina: a partir de ese momento deja de aparecer en `GET /api/v1/payroll-calendars` y se trata como no encontrada en `PATCH`/`DELETE` (`404`). El borrado **no** es destructivo: las corridas de nómina ya cerradas siguen siendo reproducibles. Si hay empleados activos o pendientes asignados al calendario, la solicitud falla — primero debes reasignarlos a otro calendario. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}` **Path param:** `payroll_calendar_number` — entero positivo, el número de calendario dentro de la organización. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/payroll-calendars/3" \ -H "Authorization: Bearer TU_API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] * **Rechaza** la solicitud (`409`) si hay empleados **activos o pendientes** asignados al calendario — la respuesta incluye `affected_count`. Reasígnalos a otro calendario y reintenta. * **Marca** el calendario como eliminado. La fila deja de aparecer en `GET /api/v1/payroll-calendars`. * **Conserva** el historial para lectura: las corridas ya cerradas siguen siendo reproducibles. ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------- | | `400` | `payroll_calendar_number` inválido en la URL. | | `404` | El calendario no existe o ya fue eliminado en esta organización. | | `409` | Hay empleados activos o pendientes asignados — la respuesta incluye `affected_count`. | Ejemplo de cuerpo `409`: ```json { "message": "Cannot deactivate a payroll calendar while active or pending employees are assigned to it. Reassign them first.", "affected_count": 12 } ``` # Editar (/docs/developers/api-reference/payroll-calendars/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente un calendario de nómina. Todos los campos del cuerpo son **opcionales**: si omites una clave, ese campo queda intacto. Solo `name`, `pay_day_offset` e `is_active` son mutables; `frequency`, la fecha ancla (`anchor_date`), `timezone` y la razón social son **inmutables**, porque cambiarlos invalidaría los periodos ya generados. El identificador en la URL es el `payroll_calendar_number` del calendario dentro de tu organización. Una solicitud exitosa devuelve `200` con la forma completa del calendario actualizado. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}` **Path param:** `payroll_calendar_number` — entero positivo, el número de calendario dentro de la organización. ## Actualizacion minima [#actualizacion-minima] *** Envía solo los campos que quieras cambiar. El resto del calendario queda intacto. ```json { "name": "Quincenal 2026 (revisado)" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/payroll-calendars/3" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Quincenal 2026 (revisado)" }' ``` **Respuesta** ```json { "payroll_calendar_number": 3, "legal_entity_number": 1, "name": "Quincenal 2026 (revisado)", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "applies_seventh_day_discount": true, "month_day_factor": 30, "anchor_date": "2026-01-01T00:00:00.000Z", "pay_day_offset": 3, "timezone": "America/Mexico_City", "is_active": true, "periods_count": 24, "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T11:15:32.000Z" } ``` ## Ajustar la fecha de pago y desactivar [#ajustar-la-fecha-de-pago-y-desactivar] *** `pay_day_offset` reajusta cuántos días después del fin del periodo cae la fecha de pago (un valor negativo la adelanta antes del cierre). `is_active: false` desactiva el calendario, pero solo si **no** hay empleados activos o pendientes asignados a él: en ese caso la solicitud falla con `409` e incluye `affected_count`. Reasigna esos empleados a otro calendario antes de desactivarlo. ```json { "pay_day_offset": 5, "is_active": false } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/payroll-calendars/3" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "pay_day_offset": 5, "is_active": false }' ``` **Respuesta** ```json { "payroll_calendar_number": 3, "legal_entity_number": 1, "name": "Quincenal 2026", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "applies_seventh_day_discount": true, "month_day_factor": 30, "anchor_date": "2026-01-01T00:00:00.000Z", "pay_day_offset": 5, "timezone": "America/Mexico_City", "is_active": false, "periods_count": 24, "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T11:18:09.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | ------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | string | 3–60 caracteres. Único por razón social. | | `pay_day_offset` | integer | Entre `-15` y `15`. Días desde el fin del periodo hasta la fecha de pago; un valor negativo paga antes del cierre. No puede ser menor que el mínimo de la periodicidad del calendario (`0` en `daily`, `-6` en `weekly`, `-7` en `decennial`, `-13` en `biweekly`, `-12` en `semimonthly` y `-15` en `monthly`); si lo es, se rechaza con `400`. | | `is_active` | boolean | Al ponerlo en `false` se rechaza (`409`) si hay empleados activos o pendientes asignados. | | `isr_tariff_frequency` | string | Tabla del SAT con la que se retiene el ISR: el mismo valor que `frequency` del calendario, o `monthly`. Cualquier otro se rechaza con `400`. No modifica los periodos ya cerrados. | | `applies_seventh_day_discount` | boolean | Si las faltas y ausencias sin goce descuentan además un sexto del día de descanso (séptimo día proporcional). Un valor que no sea booleano se rechaza con `400`. No modifica los periodos ya cerrados; los abiertos o calculados lo toman al recalcularse. | | `month_day_factor` | number | Días del mes del calendario: `30` o `30.4` (el número, no texto). Cualquier otro valor se rechaza con `400` y el mensaje `Los días del mes deben ser 30 o 30.4.` No modifica los periodos ya cerrados; los abiertos o calculados lo toman al recalcularse. | **Campos inmutables:** `frequency`, `anchor_date`, `timezone` y la razón social no se pueden editar — cambiarlos invalidaría los periodos ya generados. Para una configuración distinta, crea un calendario nuevo. ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — misma forma que una fila de [listar](/docs/developers/api-reference/payroll-calendars/list): ```json { "payroll_calendar_number": 3, "legal_entity_number": 1, "name": "Quincenal 2026", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "applies_seventh_day_discount": true, "month_day_factor": 30, "anchor_date": "2026-01-01T00:00:00.000Z", "pay_day_offset": 3, "timezone": "America/Mexico_City", "is_active": true, "periods_count": 24, "created_at": "...", "updated_at": "..." } ``` ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `payroll_calendar_number` inválido en la URL, o falla de validación del cuerpo. | | `404` | El calendario con ese `payroll_calendar_number` no existe (o fue eliminado) en esta organización. | | `409` | `is_active: false` pero hay empleados activos o pendientes asignados — la respuesta incluye `affected_count`. También cuando el `name` ya existe para la razón social del calendario. | Ejemplo de cuerpo `409` al intentar desactivar: ```json { "message": "Cannot deactivate a payroll calendar while active or pending employees are assigned to it. Reassign them first.", "affected_count": 12 } ``` # Listar (/docs/developers/api-reference/payroll-calendars/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de los calendarios de nómina de tu organización. Los resultados se devuelven en formato JSON e incluyen metadatos de paginación. Un calendario es **por razón social**: sus periodos son específicos de la razón social a la que pertenece. Los calendarios eliminados se excluyen automáticamente. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/payroll-calendars` ## Consulta multiples calendarios de nomina [#consulta-multiples-calendarios-de-nomina] *** Devuelve todos los calendarios de la organización, paginados de 20 en 20. Puedes acotar el listado por frecuencia o por su flag activo. ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | ----------- | ---------------------------------------------------- | --------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `frequency` | `weekly` \| `biweekly` \| `semimonthly` \| `monthly` | No | - | Filtra por la frecuencia del calendario. | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/payroll-calendars" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 24, "page": 1, "page_size": 20, "total_pages": 2, "next": "?page=2", "previous": null, "results": [ { "payroll_calendar_number": 3, "legal_entity_number": 1, "name": "Quincenal 2026", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "applies_seventh_day_discount": true, "month_day_factor": 30, "anchor_date": "2026-01-01T00:00:00.000Z", "pay_day_offset": 3, "timezone": "America/Mexico_City", "is_active": true, "periods_count": 24, "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" } ] } ``` ## Consulta calendarios de una razon social [#consulta-calendarios-de-una-razon-social] *** Se puede buscar un calendario particular por su número, o filtrar por la razón social a la que pertenecen. Ambos filtros hacen coincidencia exacta. ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | ------------------------- | ------- | --------- | ------- | ------------------------------------------ | | `payroll_calendar_number` | integer | No | - | Filtra por número de calendario exacto. | | `legal_entity_number` | integer | No | - | Filtra por la razón social del calendario. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/payroll-calendars?legal_entity_number=1" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "payroll_calendar_number": 3, "legal_entity_number": 1, "name": "Quincenal 2026", "frequency": "semimonthly", "isr_tariff_frequency": "semimonthly", "applies_seventh_day_discount": true, "month_day_factor": 30, "anchor_date": "2026-01-01T00:00:00.000Z", "pay_day_offset": 3, "timezone": "America/Mexico_City", "is_active": true, "periods_count": 24, "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | ---------------------------------------------------------------------------------------------------------- | | `400` | Valor inválido en algún filtro (`payroll_calendar_number`, `legal_entity_number`, `frequency` o `active`). | | `401` | API key ausente o inválida. | # Calcular (/docs/developers/api-reference/payroll-calculation/calculate) ## Descripcion general [#descripcion-general] Este endpoint **corre la nómina** de un periodo. Si el periodo todavía no está abierto, lo abre, creando la corrida y **congelando** los empleados, sus salarios y las prestaciones de sus contratos, y a continuación lo calcula. Si el periodo ya estaba abierto, simplemente lo (re)calcula. Abrir y calcular son **un solo paso**: el campo `opened` de la respuesta te dice si esta llamada abrió el periodo (`true`) o si ya estaba abierto (`false`). La llamada es **idempotente en el resultado** mientras no cambien sus insumos: repetirla sobre un periodo ya calculado lo vuelve a calcular con los mismos empleados congelados y deja los mismos resultados. Los conceptos y los catálogos fiscales se leen como están en cada cálculo, así que un cambio en un concepto se refleja al volver a calcular. Para incorporar cambios de empleados o de asistencia posteriores a la apertura, utiliza [recalcular](/docs/developers/api-reference/payroll-calculation/recalculate). No lleva cuerpo. Requiere que la API key tenga el permiso **Calcular nómina**. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}/periods/{period_number}/calculate` **Path params:** * `payroll_calendar_number` — entero positivo, el número del calendario. * `period_number` — el periodo, con el formato `{fiscal_year}-{sequence_number}` (por ejemplo `2026-14`). ## Corre un periodo [#corre-un-periodo] *** Abre el periodo (si hacía falta) y lo calcula. `entries_count` es el número de empleados calculados. ## Campos de la respuesta [#campos-de-la-respuesta] | Campo | Tipo | Descripcion | | -------------------- | ------- | ---------------------------------------------------------------------- | | `period_number` | string | El periodo calculado. | | `payroll_run_number` | integer | Número de la corrida creada o recalculada. | | `status` | string | Siempre `calculated` cuando la llamada tiene éxito. | | `entries_count` | integer | Empleados calculados en la corrida. | | `opened` | boolean | `true` si esta llamada abrió el periodo; `false` si ya estaba abierto. | **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/payroll-calendars/1/periods/2026-14/calculate" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "period_number": "2026-14", "payroll_run_number": 14, "status": "calculated", "entries_count": 277, "opened": true } ``` ## Comportamiento [#comportamiento] 1. Si el periodo no tiene corrida, la **abre**: valida el calendario y la plantilla, y congela los datos de los empleados (empleados activos del calendario, salario diario, prestaciones del contrato de cada empleado). Los conceptos y los catálogos fiscales se leen en cada cálculo. 2. En el **primer** cálculo, aplica a la corrida la asistencia capturada en el registro global para las fechas del periodo. En los cálculos posteriores respeta la asistencia ya aplicada (para volver a sincronizarla, usa [recalcular](/docs/developers/api-reference/payroll-calculation/recalculate)). 3. Ejecuta el motor de cálculo: percepciones, deducciones, ISR, IMSS, INFONAVIT, ISN y costo patronal. 4. Deja la corrida y el periodo en estado `calculated`. Al cerrar, la corrida conserva los datos con los que se calculó por última vez, así que una corrida cerrada se puede reproducir idénticamente aunque después cambien salarios, contratos, conceptos o catálogos. ## Errores [#errores] | Status | Código | Causa | | ------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | — | `payroll_calendar_number` o `period_number` inválidos en la URL. | | `401` | — | API key ausente o inválida. | | `403` | — | La API key no tiene el permiso **Calcular nómina**. | | `404` | — | El calendario o el periodo no existen en esta organización. | | `409` | `INACTIVE_CALENDAR` | El calendario está desactivado. No se pueden abrir periodos en él. | | `409` | `NO_EMPLOYEES` | El calendario no tiene empleados activos que calcular. | | `409` | `MISSING_REQUIRED_FIELDS` | Algún empleado no tiene los datos requeridos para nómina (RFC, CURP, código postal, salario, contrato). | | `409` | `OUT_OF_NORM` | Las prestaciones del contrato de algún empleado están **por debajo del mínimo de ley**. La respuesta incluye `norm_violations`. | | `409` | `CLOSED` | El periodo está cerrado y no admite nuevos cálculos. | | `409` | `CANCELLED` | La corrida del periodo fue cancelada; no se puede calcular. | | `409` | `CATALOG_MISSING` | Falta configurar un catálogo fiscal requerido para el periodo. | | `409` | `RETRO_STATE_CHANGED` | Un ajuste retroactivo cambió durante el cálculo. Vuelve a intentar. | | `409` | `HOUSING_INSURANCE_STATE_CHANGED` | Otra nómina del bimestre cambió el seguro de vivienda INFONAVIT durante el cálculo. Vuelve a intentar. | | `409` | `NO_SNAPSHOT` | La corrida no tiene una configuración congelada completa para el periodo. | | `409` | `CONCURRENT_CALCULATION` | Otro cálculo de nómina de los mismos empleados está en curso. Vuelve a intentar en un momento. | | `409` | `NOT_ONE_EMPLOYMENT` | La corrida es un finiquito con más de un empleado; un finiquito cubre a uno solo. | | `409` | `NOT_CLOSING_PERIOD` | La corrida tiene activado el ajuste anual de ISR, pero no es el último periodo ordinario del ejercicio. Desactívalo en las opciones de la corrida. | | `409` | `NO_ANNUAL_ISR_TARIFF` | La corrida aplica el ajuste anual de ISR, no está configurada la tarifa anual de ISR del ejercicio y al menos un empleado no está excluido por tipo de trabajador, no estar marcado para el ajuste, año incompleto o solicitud por escrito. | # Capturar incidencias (/docs/developers/api-reference/payroll-calculation/capture-incidences) ## Descripcion general [#descripcion-general] Este endpoint captura **un tipo de incidencia** para empleados de un periodo, dentro de su **ventana de fechas**. Es la misma captura que [Asistencia → Capturar](/docs/developers/api-reference/attendance/capture), acotada a un periodo por comodidad. Las incidencias se registran en el **registro global de asistencia**, del que la corrida lee. Si el periodo está abierto y ya calculado, **se recalcula solo** con la captura. Si el periodo está cerrado, una captura dentro de sus fechas se convierte en un **ajuste retroactivo** y requiere `confirm_retro: true`. Las **horas extra** (`OVERTIME_DOUBLE_HOURS` y `OVERTIME_TRIPLE_HOURS`) capturadas por la API se crean **pendientes de autorizar**: no se pagan hasta que las autorizan todos los niveles configurados en la organización (jefe directo, Recursos Humanos o los dos). Su estatus se consulta en el campo `approval_status` de [Listar asistencia](/docs/developers/api-reference/attendance/list#estatus-de-autorizacion). Requiere que la API key tenga el permiso **Capturar incidencias del periodo**. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}/periods/{period_number}/incidences` **Path params:** * `payroll_calendar_number` — entero positivo, el número del calendario. * `period_number` — el periodo, con el formato `{fiscal_year}-{sequence_number}` (por ejemplo `2026-11`). ## Captura para el periodo [#captura-para-el-periodo] *** El cuerpo es el mismo de la captura global, sin el rango obligatorio: `start_date` / `end_date` son **opcionales** y por omisión abarcan **todo el periodo**. Si los envías, deben caer **dentro** de la ventana del periodo (de lo contrario `409 DATE_OUT_OF_PERIOD`). Cuando el periodo ya está abierto, la captura se restringe a su **plantilla** (los empleados de la corrida): un `employee_number` que no esté en ella se rechaza con `NOT_IN_ROSTER`. ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Requerido | Notas | | ------------------------- | --------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `incidence_code` | string | Sí | Código del tipo, resuelto en la razón social de los empleados. | | `employee_numbers` | integer\[] | Sí | 1–500 empleados, misma razón social. Con el periodo abierto, deben estar en la plantilla. | | `start_date` / `end_date` | string (`YYYY-MM-DD`) | No | Por omisión, la ventana del periodo. Si se envían, deben caer dentro de ella. | | `quantity` | number | Condicional | Requerida para tipos `hours` y `count`. En `VACATION_PAID` y `VACATION_UNPAID`, `0.5` registra medio día por fecha y cualquier otro valor se registra como `1` (día completo, por omisión); en los demás tipos `days` se ignora y se registra `1`. | | `notes` | string | No | Máximo 500 caracteres. | | `disability_folio` | string | No | Folio del certificado de incapacidad, máximo 30 caracteres. Solo para tipos de incapacidad y un solo empleado. Se guarda en mayúsculas. Un folio pertenece a un solo empleado y sus días deben ser consecutivos. | | `confirm_retro` | boolean | No | Autoriza el ajuste retroactivo si el periodo está cerrado. | **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/payroll-calendars/1/periods/2026-11/incidences" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "incidence_code": "FALTA", "employee_numbers": [1, 2] }' ``` **Respuesta** ```json { "created": 2, "retros_touched": 0, "recalc_pending": false, "period_number": "2026-11" } ``` ## Después de capturar: recalcula [#después-de-capturar-recalcula] Un periodo abierto ya calculado **se recalcula solo** con cada captura: no necesitas hacer nada más para que los resultados la reflejen. La respuesta lo confirma con `recalc_pending: false`. Cuando llega en `true` — el recálculo automático no se completó — llama a [recalcular](/docs/developers/api-reference/payroll-calculation/recalculate); hasta entonces el periodo queda con un cálculo **desactualizado**, y [cerrar](/docs/developers/api-reference/payroll-calculation/close) lo rechaza con `STALE_CALCULATION`. ## Errores [#errores] | Status | Código | Causa | | ------ | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | — | `payroll_calendar_number` / `period_number` inválidos, o cuerpo mal formado. | | `400` | `EMPLOYEE_NOT_FOUND` / `NO_ACTIVE_EMPLOYMENT` | Empleado inexistente o sin empleo activo/pendiente. | | `400` | `NOT_IN_ROSTER` | Un empleado no pertenece a la corrida del periodo. | | `400` | `LEGAL_ENTITY_MISMATCH` | Los empleados no comparten la razón social del tipo. | | `400` | `INCIDENCE_NOT_FOUND` | Sin tipo activo con ese código en la razón social. | | `400` | `RANGE_TOO_LARGE` | El rango excede 366 días. | | `400` | `DISABILITY_FOLIO_NOT_ALLOWED` | Se envió `disability_folio` con un tipo que no es incapacidad. | | `400` | `DISABILITY_FOLIO_MULTIPLE_EMPLOYEES` | Se envió `disability_folio` para más de un empleado. | | `401` | — | API key ausente o inválida. | | `403` | — | La API key no tiene el permiso **Capturar incidencias del periodo**. | | `404` | — | El calendario o el periodo no existen en esta organización. | | `409` | `DATE_OUT_OF_PERIOD` | Una fecha cae fuera de la ventana del periodo. | | `409` | `DAY_CONFLICT` | Algún empleado ya tiene otro estatus de día en una de las fechas. Con `disability_folio`, también cuenta una incapacidad del mismo tipo ya capturada. | | `409` | `OVERTIME_REJECTED` | Algún empleado ya tiene horas extra rechazadas de ese tipo, sin checadas vinculadas, en una de las fechas. No se captura nada; el registro rechazado se corrige en la plataforma, en **Incidencias de tiempo**: cambia sus horas para enviarlo de nuevo a autorización, o elimínalo y vuelve a capturar. | | `409` | `RETRO_CONFIRMATION_REQUIRED` | El periodo está cerrado. Reenvía con `confirm_retro: true`. | | `409` | `DISABILITY_FOLIO_TAKEN` | El folio ya está registrado para otro empleado. No se captura nada. | | `409` | `DISABILITY_FOLIO_NOT_CONSECUTIVE` | El empleado ya tiene ese folio en fechas que no son consecutivas con las enviadas. No se captura nada. | # Cerrar (/docs/developers/api-reference/payroll-calculation/close) ## Descripcion general [#descripcion-general] Este endpoint **cierra** el periodo: da la corrida por definitiva y deja tanto la corrida como el periodo en estado `closed`. Es el último paso del flujo de cálculo. Solo se puede cerrar un periodo en estado `calculated`, y el cálculo debe estar **al día**: si se capturó o cambió algo después del último cálculo, el cierre se rechaza con `STALE_CALCULATION` — recalcula y vuelve a cerrar. Cerrar es **definitivo**: la API no expone la reapertura. Al cerrar, el periodo **congela** su historial de asistencia y marca las incidencias que consumió. A partir de ahí, cualquier incidencia capturada dentro de sus fechas se convierte en un **ajuste retroactivo** que corrige la diferencia en el siguiente periodo (ver [capturar incidencias](/docs/developers/api-reference/payroll-calculation/capture-incidences)); no reescribe la nómina ya pagada. En una corrida ordinaria, el cierre también registra en el saldo de vacaciones de cada empleado un movimiento de uso, con días negativos y fechado en el día, por cada día de vacaciones pagado desde incidencias capturadas directamente; los días respaldados por una solicitud de tiempo libre aprobada se omiten porque la aprobación ya los registró. Si la corrida paga ajustes retroactivos de periodos ordinarios ya cerrados, el cierre también concilia sus vacaciones: un día capturado tarde se registra, y uno editado o eliminado se revierte con un movimiento de signo contrario. Así, un cambio a un día de vacaciones de un periodo cerrado mueve el saldo cuando se cierra la nómina que paga su ajuste retroactivo. Después de responder, el cierre abre en segundo plano la corrida del siguiente periodo del mismo calendario (o, tras el último del año fiscal, el primero del siguiente), siempre que ese periodo ya esté generado y no esté cerrado, que ningún periodo posterior del calendario tenga ya una corrida y que la API key tenga también el permiso **Calcular nómina**; por eso cerrar un periodo anterior no abre uno intermedio. Si la apertura no se puede completar, el cierre no se afecta y la respuesta es la misma: la bitácora de la corrida cerrada muestra el evento **Siguiente periodo sin abrir**. Las corridas extraordinarias y los finiquitos no descuentan vacaciones ni abren ninguna corrida. Cerrar no timbra los recibos: el timbrado ante el SAT es un paso aparte, con [timbrar](/docs/developers/api-reference/payroll-calculation/stamp). Los resultados siguen siendo consultables después del cierre: el [detalle del periodo](/docs/developers/api-reference/payroll-calculation/detail) y la [exportación a Excel](/docs/developers/api-reference/payroll-calculation/export-excel) funcionan igual con un periodo cerrado. No lleva cuerpo. Requiere que la API key tenga el permiso **Cerrar nómina**. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}/periods/{period_number}/close` **Path params:** * `payroll_calendar_number` — entero positivo, el número del calendario. * `period_number` — el periodo, con el formato `{fiscal_year}-{sequence_number}` (por ejemplo `2026-14`). ## Cierra el periodo [#cierra-el-periodo] *** Marca la corrida y el periodo como cerrados y registra la fecha de cierre en la bitácora de la corrida. ## Campos de la respuesta [#campos-de-la-respuesta] | Campo | Tipo | Descripcion | | -------------------- | -------------- | ----------------------------------------------- | | `period_number` | string | El periodo cerrado. | | `payroll_run_number` | integer | Número de la corrida. | | `status` | string | Siempre `closed` cuando la llamada tiene éxito. | | `closed_at` | string (fecha) | Momento del cierre. | **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/payroll-calendars/1/periods/2026-14/close" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "period_number": "2026-14", "payroll_run_number": 14, "status": "closed", "closed_at": "2026-07-31T18:16:07.787Z" } ``` ## Errores [#errores] | Status | Código | Causa | | ------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | — | `payroll_calendar_number` o `period_number` inválidos en la URL. | | `401` | — | API key ausente o inválida. | | `403` | — | La API key no tiene el permiso **Cerrar nómina**. | | `404` | — | El calendario o el periodo no existen en esta organización. | | `409` | `NOT_CALCULATED` | El periodo no está calculado (o nunca se abrió). Córrelo primero con [calcular](/docs/developers/api-reference/payroll-calculation/calculate). | | `409` | `CLOSED` | El periodo ya está cerrado. | | `409` | `STALE_CALCULATION` | Se capturó o cambió algo después del último cálculo. Recalcula con [recalcular](/docs/developers/api-reference/payroll-calculation/recalculate) y vuelve a cerrar. | | `409` | `HOUSING_INSURANCE_STALE` | Quién debe el seguro de vivienda INFONAVIT del bimestre cambió después del último cálculo (por ejemplo, se cerró el primer periodo del bimestre). Recalcula con [recalcular](/docs/developers/api-reference/payroll-calculation/recalculate) y vuelve a cerrar. | # Detalle (/docs/developers/api-reference/payroll-calculation/detail) ## Descripcion general [#descripcion-general] Este endpoint devuelve la información de un **periodo**: sus fechas, su estado y —cuando ya fue calculado— los **totales de la corrida** y el **detalle por empleado**. Si el periodo aún no se ha abierto, la respuesta llega con `status: "unopened"`, `totals` en `null` y `entries` vacío: el periodo existe, simplemente no tiene resultados todavía. Requiere que la API key tenga el permiso **Listar periodos de nómina**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}/periods/{period_number}` **Path params:** * `payroll_calendar_number` — entero positivo, el número del calendario. * `period_number` — el periodo, con el formato `{fiscal_year}-{sequence_number}` (por ejemplo `2026-14`). Un calendario puede abarcar más de un año fiscal, por eso el identificador lleva el año. ## Consulta un periodo calculado [#consulta-un-periodo-calculado] *** Devuelve el periodo con sus totales y una fila por empleado. `entries` trae **todos** los empleados de la corrida (no está paginado); para un desglose completo por concepto, usa la [exportación a Excel](/docs/developers/api-reference/payroll-calculation/export-excel). **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/payroll-calendars/1/periods/2026-14" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "period_number": "2026-14", "fiscal_year": 2026, "sequence_number": 14, "payroll_calendar_number": 1, "calendar_name": "Quincenal 2026", "legal_entity_number": 1, "frequency": "semimonthly", "start_date": "2026-07-16T00:00:00.000Z", "end_date": "2026-07-31T00:00:00.000Z", "pay_date": "2026-08-05T00:00:00.000Z", "days_in_period": 16, "accrual_month": 7, "status": "calculated", "payroll_run_number": 14, "employee_count": 277, "calculated_at": "2026-07-13T17:10:00.000Z", "closed_at": null, "recalc_pending": false, "totals": { "total_perceptions": "2575969.97", "total_non_cash": "0", "total_withheld_food_vouchers": "0", "total_deductions": "360871.85", "total_isr": "291712.52", "total_subsidio": "0", "total_imss_worker": "69159.31", "total_net": "2215098.20", "total_imss_employer": "1843299.76", "total_infonavit": "135150.09", "total_isn": "0", "total_employer_cost": "1978450.07", "total_company_cost": "4554420.09", "deduction_lines": [ { "code": "isr", "name": "ISR retenido", "amount": "291712.52" }, { "code": "imss_worker", "name": "IMSS trabajador", "amount": "69159.31" } ], "employer_lines": [ { "code": "imss_employer", "name": "IMSS patronal", "amount": "1843299.76" }, { "code": "infonavit_employer", "name": "INFONAVIT patronal", "amount": "135150.09" } ], "imss_employer_lines": { "partial": false, "lines": [ { "code": "imss_e_cuota_fija", "name": "Cuota fija enf. y mat.", "amount": "412508.31" }, { "code": "imss_e_riesgo_trabajo", "name": "Riesgo de trabajo", "amount": "108420.55" }, { "code": "imss_e_invalidez_vida", "name": "Invalidez y vida", "amount": "96331.12" }, { "code": "imss_e_guarderias", "name": "Guarderías y prest. sociales", "amount": "88190.42" } ] } }, "entries": [ { "employee_number": 1, "employee_name": "Juan Carlos Pérez López", "total_perceptions": "6000", "total_deductions": "627.05", "isr": "473.46", "imss_worker": "153.6", "net_pay": "5372.95", "has_retro_adjustment": false, "period_days": 16, "paid_days": 15, "absence_days": 1, "disability_days": 0, "incidence_count_internal": 1, "incidence_count_external": 0, "daily_salary": "375", "sbc_daily": "393.75", "subsidio": "0", "withheld_food_vouchers": "0", "other_deductions": "0", "imss_employer": "1063.13", "infonavit_employer": "0", "isn": "0", "total_employer_cost": "1063.13", "total_company_cost": "7063.13" } ] } ``` ## Consulta un periodo sin abrir [#consulta-un-periodo-sin-abrir] *** Un periodo que nunca se ha corrido **no es un error**: responde `200` con `status: "unopened"`, sin totales ni detalle. Para correrlo, llama a [calcular](/docs/developers/api-reference/payroll-calculation/calculate) — abre el periodo y lo calcula en una sola llamada. **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/payroll-calendars/1/periods/2026-20" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "period_number": "2026-20", "fiscal_year": 2026, "sequence_number": 20, "payroll_calendar_number": 1, "calendar_name": "Quincenal 2026", "legal_entity_number": 1, "frequency": "semimonthly", "start_date": "2026-10-16T00:00:00.000Z", "end_date": "2026-10-31T00:00:00.000Z", "pay_date": "2026-11-05T00:00:00.000Z", "days_in_period": 16, "accrual_month": 10, "status": "unopened", "payroll_run_number": null, "employee_count": null, "calculated_at": null, "closed_at": null, "totals": null, "entries": [] } ``` ## Campos de la respuesta [#campos-de-la-respuesta] | Campo | Tipo | Descripcion | | ------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `period_number` | string | Identificador del periodo (`{fiscal_year}-{sequence_number}`). | | `payroll_calendar_number` | integer | Calendario al que pertenece. | | `calendar_name` | string | Nombre del calendario. | | `legal_entity_number` | integer | Razón social del calendario. | | `frequency` | string | Frecuencia del calendario (`weekly`, `biweekly`, `semimonthly`, `monthly`). | | `accrual_month` | integer | Mes de acumulación del periodo, de `1` a `12`: el mes de la ventana mensual de ISR en que cuenta. Los periodos que nunca tuvieron un mes asignado devuelven el mes de su fecha de fin. | | `status` | string | `unopened`, `draft`, `calculated` o `closed`. | | `payroll_run_number` | integer \| null | Número de corrida. | | `employee_count` | integer \| null | Empleados incluidos en la corrida. | | `recalc_pending` | boolean | `true` cuando la corrida quedó desfasada del registro de incidencias: las cifras de esta respuesta no reflejan el estado vigente hasta que se vuelva a calcular. Ver abajo. | | `totals` | objeto \| null | Totales de la corrida. `null` mientras no esté calculada. | | `entries` | array | Una fila por empleado. Vacío mientras no esté calculada. | ### `totals` [#totals] | Campo | Descripcion | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `total_perceptions` | Percepciones totales. | | `total_non_cash` | Percepciones no monetarias (por ejemplo, una prestación en especie). Los vales de despensa en tarjeta no cuentan aquí cuando el recibo los descuenta en **Vales de despensa entregados**. | | `total_withheld_food_vouchers` | Vales de despensa entregados en tarjeta que la corrida descuenta en **Vales de despensa entregados**. Ya están incluidos en `total_deductions`. | | `total_deductions` | Deducciones totales. | | `total_isr` | ISR retenido. | | `total_subsidio` | Subsidio para el empleo aplicado contra el ISR en toda la corrida. Ya está descontado de `total_isr`. El subsidio que excede el ISR se paga como la percepción **Subsidio para el empleo**, sin pasar del subsidio del periodo (con la tarifa del periodo, en proporción a los días pagados), y está incluido en `total_perceptions`. En algunas nóminas cerradas, ese excedente quedó como informativo, no se pagó y no está incluido. | | `total_imss_worker` | IMSS a cargo del trabajador. | | `total_net` | Neto a depositar. | | `total_imss_employer` | IMSS patronal. | | `total_infonavit` | INFONAVIT patronal. | | `total_isn` | Impuesto sobre nómina. | | `total_employer_cost` | Costo patronal. | | `total_company_cost` | Costo total para la empresa. | | `deduction_lines` | Desglose de las deducciones por concepto (`code`, `name`, `amount`). | | `employer_lines` | Desglose del costo patronal por concepto. | | `imss_employer_lines` | IMSS patronal desglosado **por rama** (cuota fija, riesgo de trabajo, invalidez y vida, guarderías, retiro, cesantía y vejez, …). Ver abajo. | ### `entries` [#entries] | Campo | Descripcion | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `employee_number` | Número del empleado dentro de la organización. | | `employee_name` | Nombre completo del empleado. | | `total_perceptions` | Percepciones del empleado en el periodo. | | `total_deductions` | Deducciones del empleado. | | `isr` | ISR retenido al empleado. | | `imss_worker` | IMSS a cargo del empleado. | | `net_pay` | Neto a depositar al empleado. | | `has_retro_adjustment` | `true` si el recibo incluye líneas de ajuste retroactivo. | | `period_days` | Días que abarca el periodo. | | `paid_days` | Días pagados de sueldo. | | `absence_days` | Días de falta descontados. | | `disability_days` | Días de incapacidad. | | `incidence_count_internal` | Incidencias del periodo capturadas dentro de la plataforma. | | `incidence_count_external` | Incidencias del periodo provenientes de una fuente externa (API, reloj checador). | | `daily_salary` | Salario diario. | | `sbc_daily` | Salario base de cotización diario. | | `subsidio` | Subsidio para el empleo aplicado contra el ISR del empleado. Ya está descontado de `isr`. El subsidio que excede el ISR se paga como la percepción **Subsidio para el empleo**, sin pasar del subsidio del periodo (con la tarifa del periodo, en proporción a los días pagados), y está incluido en `total_perceptions`. En algunas nóminas cerradas, ese excedente quedó como informativo, no se pagó y no está incluido. | | `withheld_food_vouchers` | Vales de despensa entregados en tarjeta que el recibo descuenta en **Vales de despensa entregados**. | | `other_deductions` | Deducciones distintas de ISR e IMSS trabajador. Incluye `withheld_food_vouchers`. | | `imss_employer` | IMSS patronal del empleado. | | `infonavit_employer` | INFONAVIT patronal del empleado. | | `isn` | Impuesto sobre nómina del empleado. | | `total_employer_cost` | Costo patronal del empleado. | | `total_company_cost` | Costo total para la empresa por ese empleado. | ## IMSS patronal por rama (`imss_employer_lines`) [#imss-patronal-por-rama-imss_employer_lines] *** `employer_lines` trae el IMSS patronal como una sola cifra. `imss_employer_lines` lo abre por **rama de seguro**, que es el desglose que piden la conciliación con el SUA y la contabilidad por centro de costo. | Campo | Descripcion | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `lines` | Una entrada por rama, con `code`, `name` e `amount`. | | `partial` | `true` cuando alguna corrida no conserva los insumos congelados necesarios para reconstruir el desglose. Esos empleados **no** aparecen en `lines`, pero sus cuotas sí están en `total_imss_employer`: en ese caso la suma de `lines` es menor que el total. | El desglose se recalcula con la **misma función del motor** que se usó al calcular la nómina, sobre los insumos congelados de la corrida — no es una reestimación. ## Sincronización pendiente (`recalc_pending`) [#sincronización-pendiente-recalc_pending] *** Los periodos abiertos son una ventana viva sobre el registro de incidencias: cada captura vuelve a calcular la corrida en tiempo real, así que normalmente `recalc_pending` llega en `false`. Llega en `true` cuando esa actualización automática no se completó o el registro se modificó fuera de la plataforma. Las cifras siguen siendo las del último cálculo — vuelve a calcular con [recalculate](/docs/developers/api-reference/payroll-calculation/recalculate) para aplicar el estado vigente. Cerrar un periodo desfasado está bloqueado, así que nada desactualizado se paga. Todos los importes se devuelven como **string** para no perder precisión decimal. ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `payroll_calendar_number` inválido, o `period_number` con formato incorrecto (debe ser `{fiscal_year}-{sequence_number}`, por ejemplo `2026-14`). | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Listar periodos de nómina**. | | `404` | El calendario o el periodo no existen en esta organización. | # Exportar nómina ciega (/docs/developers/api-reference/payroll-calculation/export-blind-excel) ## Descripcion general [#descripcion-general] Este endpoint descarga la **nómina ciega** del periodo en un archivo **Excel (`.xlsx`)**: el libro de autorización que se comparte para firma. Trae los **totales globales** de la corrida y, por persona, únicamente **días e incidencias** — nunca el importe individual de nadie. Es el mismo libro que la plataforma genera desde la corrida, para cuando alguien debe autorizar la nómina sin ver los sueldos de cada empleado. A diferencia del resto de la API, la respuesta **no es JSON**: es el archivo binario. Solo los errores se devuelven en JSON. Requiere un periodo `calculated` o `closed` (un periodo sin abrir o en `draft` todavía no tiene resultados que exportar). Requiere que la API key tenga el permiso **Exportar nómina**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}/periods/{period_number}/export-blind-excel` **Path params:** * `payroll_calendar_number` — entero positivo, el número del calendario. * `period_number` — el periodo, con el formato `{fiscal_year}-{sequence_number}` (por ejemplo `2026-14`). ## Descarga el libro de autorización [#descarga-el-libro-de-autorización] *** La respuesta es el archivo `.xlsx` tal cual (no va en base64 ni envuelto en JSON). El nombre del archivo viaja en el encabezado `Content-Disposition`: con **`curl -OJ`** se guarda como `nomina-ciega-corrida-14.xlsx`. Algunos clientes REST ignoran ese encabezado y guardan la respuesta **sin extensión** — el archivo es correcto, solo renómbralo agregando `.xlsx`. **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/payroll-calendars/1/periods/2026-14/export-blind-excel" \ -H "Authorization: Bearer TU_API_KEY" \ -OJ ``` **Respuesta** ```http HTTP/1.1 200 OK Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Content-Disposition: attachment; filename="nomina-ciega-corrida-14.xlsx" Cache-Control: no-store ``` ## Contenido del libro [#contenido-del-libro] Dos hojas, ambas con el encabezado de la corrida (razón social, calendario, periodo y fecha de generación). ### Hoja 1 — `Autorización` [#hoja-1--autorización] Los totales de la corrida: **Generales** (empleados, percepciones, deducciones y neto), **Percepciones por concepto**, **Deducciones por concepto**, y **Costo patronal** por concepto con el costo total empresa. ### Hoja 2 — `Personal` [#hoja-2--personal] Una fila por empleado, **sin importes**: | Columna | Descripcion | | ------------------------------------- | -------------------------------------------------- | | Número de empleado / Nombre completo | Identidad del empleado. | | Puesto | Puesto del empleado. | | Días pagados / Faltas / Incapacidades | Días del periodo para esa persona. | | Incidencias | Cuántas incidencias del periodo tiene registradas. | ## Diferencia con la exportación completa [#diferencia-con-la-exportación-completa] | | Nómina ciega | [Exportación completa](/docs/developers/api-reference/payroll-calculation/export-excel) | | ---------------------------------- | ------------ | --------------------------------------------------------------------------------------- | | Totales de la corrida | Sí | Sí | | Importes por empleado | **No** | Sí | | Días e incidencias por empleado | Sí | Sí | | Desglose por concepto por empleado | No | Sí | ## Errores [#errores] Los errores sí se devuelven en JSON. | Status | Código | Causa | | ------ | ---------------- | ---------------------------------------------------------------------------------- | | `400` | — | `payroll_calendar_number` o `period_number` inválidos en la URL. | | `401` | — | API key ausente o inválida. | | `403` | — | La API key no tiene el permiso **Exportar nómina**. | | `404` | — | El calendario o el periodo no existen, o la corrida no tiene empleados calculados. | | `409` | `NOT_CALCULATED` | El periodo aún no se ha calculado, así que no hay resultados que exportar. | # Exportar nómina de un empleado (/docs/developers/api-reference/payroll-calculation/export-employee-excel) ## Descripcion general [#descripcion-general] Este endpoint descarga la nómina de **un solo empleado** dentro de un periodo, en un archivo **Excel (`.xlsx`)**: su recibo con el **desglose completo por concepto** y las **incidencias del periodo** de esa persona. Es el detalle que la exportación de la corrida no da por empleado — útil para aclaraciones individuales, sin descargar el libro completo. A diferencia del resto de la API, la respuesta **no es JSON**: es el archivo binario. Solo los errores se devuelven en JSON. Requiere un periodo `calculated` o `closed`, y que el empleado forme parte de la corrida. Requiere que la API key tenga el permiso **Exportar nómina**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}/periods/{period_number}/employees/{employee_number}/export-excel` **Path params:** * `payroll_calendar_number` — entero positivo, el número del calendario. * `period_number` — el periodo, con el formato `{fiscal_year}-{sequence_number}` (por ejemplo `2026-14`). * `employee_number` — entero positivo, el número del empleado dentro de la organización. ## Descarga el recibo del empleado [#descarga-el-recibo-del-empleado] *** La respuesta es el archivo `.xlsx` tal cual (no va en base64 ni envuelto en JSON). El nombre del archivo viaja en el encabezado `Content-Disposition`: con **`curl -OJ`** se guarda como `nomina-{employee_number}-corrida-{run}.xlsx`. Algunos clientes REST ignoran ese encabezado y guardan la respuesta **sin extensión** — el archivo es correcto, solo renómbralo agregando `.xlsx`. El empleado debe tener un cálculo **en esa corrida**. Un empleado que existe en la organización pero no forma parte del periodo devuelve `404 NOT_IN_RUN`. **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/payroll-calendars/1/periods/2026-14/employees/1/export-excel" \ -H "Authorization: Bearer TU_API_KEY" \ -OJ ``` **Respuesta** ```http HTTP/1.1 200 OK Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Content-Disposition: attachment; filename="nomina-1-corrida-14.xlsx" Cache-Control: no-store ``` ## Contenido del libro [#contenido-del-libro] Dos hojas, con el encabezado del periodo (razón social, periodo, fechas y fecha de pago). ### Hoja 1 — `Recibo` [#hoja-1--recibo] El recibo del empleado por secciones: **Identificación** (número, nombre, RFC, CURP, NSS, puesto, razón social y fecha de pago), **Días y bases** (días del periodo, pagados, faltas, incapacidades, salario diario, SBC y factor de integración), y el desglose por concepto de **percepciones** (código, concepto, gravado, exento y total), **deducciones** y **costo patronal** (código, concepto e importe), con los totales del empleado y su neto a depositar. ### Hoja 2 — `Incidencias` [#hoja-2--incidencias] Una fila por incidencia del periodo para ese empleado, con su **origen de captura** marcado por color: | Columna | Descripcion | | ------------- | -------------------------------------------------------------------------------------------- | | Fecha | Día de la incidencia. | | Tipo | Nombre del tipo de incidencia. | | Cantidad | Cantidad registrada. | | Unidad | Unidad del tipo (`days`, `hours` o `count`). | | Origen | Interno (equipo de nómina) o externo (portal de colaborador, reloj checador, integraciones). | | Capturado por | Quién registró la incidencia. | | Notas | Notas de la captura. | ## Errores [#errores] Los errores sí se devuelven en JSON. | Status | Código | Causa | | ------ | ---------------- | ----------------------------------------------------------------------------------- | | `400` | — | `payroll_calendar_number`, `period_number` o `employee_number` inválidos en la URL. | | `401` | — | API key ausente o inválida. | | `403` | — | La API key no tiene el permiso **Exportar nómina**. | | `404` | — | El calendario o el periodo no existen en esta organización. | | `404` | `NOT_IN_RUN` | El empleado no tiene cálculo en la corrida de ese periodo. | | `409` | `NOT_CALCULATED` | El periodo aún no se ha calculado, así que no hay resultados que exportar. | # Exportar a Excel (/docs/developers/api-reference/payroll-calculation/export-excel) ## Descripcion general [#descripcion-general] Este endpoint descarga los resultados de un periodo en un archivo **Excel (`.xlsx`)**: un libro de **tres hojas** — totales de la corrida, detalle por empleado y conceptos fila por fila. A diferencia del resto de la API, la respuesta **no es JSON**: es el archivo binario. Solo los errores se devuelven en JSON. Requiere un periodo `calculated` o `closed` (un periodo sin abrir o en `draft` todavía no tiene resultados que exportar). Requiere que la API key tenga el permiso **Exportar nómina**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}/periods/{period_number}/export-excel` **Path params:** * `payroll_calendar_number` — entero positivo, el número del calendario. * `period_number` — el periodo, con el formato `{fiscal_year}-{sequence_number}` (por ejemplo `2026-14`). ## Descarga el libro de la corrida [#descarga-el-libro-de-la-corrida] *** La respuesta es el archivo `.xlsx` tal cual (no va en base64 ni envuelto en JSON). El nombre del archivo viaja en el encabezado `Content-Disposition`: con **`curl -OJ`** se guarda como `nomina-corrida-14.xlsx`. Algunos clientes REST ignoran ese encabezado y guardan la respuesta **sin extensión** — el archivo es correcto, solo renómbralo agregando `.xlsx`. **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/payroll-calendars/1/periods/2026-14/export-excel" \ -H "Authorization: Bearer TU_API_KEY" \ -OJ ``` **Respuesta** ```http HTTP/1.1 200 OK Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Content-Disposition: attachment; filename="nomina-corrida-14.xlsx" Cache-Control: no-store ``` ## Contenido del libro [#contenido-del-libro] Las tres hojas llevan el encabezado de la corrida (razón social, calendario, periodo y fecha de generación). ### Hoja 1 — `Resumen` [#hoja-1--resumen] Los totales de la corrida, agrupados por sección: **Generales** (empleados, percepciones, percepciones no monetarias, deducciones y neto), **Percepciones por concepto** (gravado, exento y total), **Deducciones por concepto**, y **Costo patronal** por concepto con el costo total empresa. Cada concepto del catálogo aparece con su clave y su nombre (`P001 - Sueldo`). Cuando los datos lo permiten, agrega **IMSS patrón desglosado por rama**. Si algún empleado no cuenta con ese desglose, la hoja lo advierte con una nota y sus cuotas se reflejan solo en el total de IMSS patronal. ### Hoja 2 — `Detalle` [#hoja-2--detalle] Formato ancho: **una fila por empleado y una columna por concepto**, con una fila final de totales. Las columnas de identidad y los encabezados quedan congelados para poder desplazarte. | Columna | Descripcion | | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Número de empleado / Nombre completo | Identidad del empleado. | | RFC / NSS / Puesto | Datos fiscales y laborales. | | Banco / CLABE | Datos bancarios para la dispersión. | | Días del periodo / Días pagados / Faltas / Incapacidades | Días del periodo y su desglose. | | Salario diario / SBC / Factor de integración | Bases salariales. | | *(una columna por concepto)* | Percepciones y deducciones, cada concepto en su columna; los demás conceptos a cargo del patrón van después de las cuotas. El encabezado lleva la clave y el nombre (`P001 - Sueldo`), o solo el nombre si no es un concepto del catálogo. | | Total percepciones / Percepciones no monetarias / Total deducciones | Totales del empleado. | | Neto a depositar | Lo que se deposita al empleado. | | Cuotas IMSS e INFONAVIT | Quince columnas fijas con las cuotas del patrón (`PAT`) y del empleado (`EMP`), en este orden: `4001 Riesgo de trabajo - PAT`, `4002 Cuota fija enf. y mat. - PAT`, `4003 Excedente 3 UMA - PAT`, `4004 Excedente 3 UMA - EMP`, `4005 Prestaciones en dinero - PAT`, `4006 Prestaciones en dinero - EMP`, `4007 Gastos médicos pensionados - PAT`, `4008 Gastos médicos pensionados - EMP`, `4009 Invalidez y vida - PAT`, `4010 Invalidez y vida - EMP`, `4011 Guarderías y prest. sociales - PAT`, `I005 Retiro (SAR) - PAT`, `4012 Cesantía y vejez - patronal`, `4013 Cesantía y vejez - EMP` e `I004 INFONAVIT patronal`. Estas claves son etiquetas del reporte, no conceptos del catálogo. Sin desglose por rama, el archivo muestra `O001 - IMSS patronal` y `O002 - INFONAVIT patronal`; si solo algunos empleados no lo tienen, conserva `O001 - IMSS patronal` para ellos. Cuando el patrón absorbe las cuotas de un empleado, sus columnas `EMP` quedan en cero. | | Costo social patronal | Todo el costo a cargo del patrón: IMSS, INFONAVIT, impuesto sobre nómina, vales y demás conceptos. | | Costo social empleado | IMSS descontado al empleado en el recibo. Es informativo y no se suma al costo total empresa. | | Costo total empresa | Costo del empleado para la empresa. | ### Hoja 3 — `Conceptos` [#hoja-3--conceptos] Formato largo: **una fila por empleado y concepto**, pensada para tablas dinámicas. | Columna | Descripcion | | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Número de empleado / Nombre completo | Identidad del empleado. | | Tipo | Percepción, deducción o costo patronal. | | Código / Concepto | Clave del concepto en el recibo (`P001`) y su nombre. En lo que no es un concepto del catálogo, **Código** lleva el identificador de cálculo de la línea (`isn`). | | Gravado / Exento / Importe | Importes del concepto para ese empleado. | ## Errores [#errores] Los errores sí se devuelven en JSON. | Status | Código | Causa | | ------ | ---------------- | -------------------------------------------------------------------------- | | `400` | — | `payroll_calendar_number` o `period_number` inválidos en la URL. | | `401` | — | API key ausente o inválida. | | `403` | — | La API key no tiene el permiso **Exportar nómina**. | | `404` | — | El calendario o el periodo no existen en esta organización. | | `409` | `NOT_CALCULATED` | El periodo aún no se ha calculado, así que no hay resultados que exportar. | # Listar incidencias (/docs/developers/api-reference/payroll-calculation/list-incidences) ## Descripcion general [#descripcion-general] Este endpoint lista las incidencias del **registro global de asistencia** que caen dentro de la **ventana de fechas** del periodo, para los empleados del calendario — las que el cálculo de esa corrida lee (la nómina lee el registro global en vivo, por fecha). El cálculo solo paga las filas con `approval_status: "approved"`: las horas extra pendientes de autorizar o rechazadas aparecen en el listado, pero no se pagan. Es la forma de verificar qué va a ver la nómina de un periodo sin filtrar el registro global a mano. Requiere que la API key tenga el permiso **Listar periodos de nómina**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}/periods/{period_number}/incidences` **Path params:** * `payroll_calendar_number` — entero positivo, el número del calendario. * `period_number` — el periodo, con el formato `{fiscal_year}-{sequence_number}` (por ejemplo `2026-11`). ## Consulta las incidencias del periodo [#consulta-las-incidencias-del-periodo] *** Devuelve las incidencias de la corrida, paginadas de 20 en 20 y ordenadas por fecha. ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | ----------------- | ------------------------ | --------- | ------- | ------------------------------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `employee_number` | integer | No | - | Filtra por empleado. | | `incidence_code` | string | No | - | Filtra por el código del tipo de incidencia (por ejemplo `FALTA`). | | `source` | string | No | - | Filtra por el origen exacto de la captura. Repetible para incluir varios. | | `origin` | `internal` \| `external` | No | - | Filtra por la clasificación del origen. Se puede combinar con `source`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/payroll-calendars/1/periods/2026-11/incidences" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 2, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "date": "2026-06-03", "employee_number": 1, "employee_name": "Juan Carlos Pérez López", "incidence_code": "FALTA", "incidence_name": "Falta injustificada", "unit": "days", "quantity": 1, "notes": null, "approval_status": "approved", "source": "attendance_module", "origin": "internal", "captured_by": "Ana Martínez", "updated_by": null, "created_at": "2026-06-16T10:00:00.000Z", "updated_at": "2026-06-16T10:00:00.000Z" }, { "date": "2026-06-05", "employee_number": 2, "employee_name": "María Fernanda Ruiz Ortiz", "incidence_code": "OVERTIME_DOUBLE_HOURS", "incidence_name": "Horas extra dobles", "unit": "hours", "quantity": 3, "notes": "turno extra", "approval_status": "pending", "source": "attendance_module", "origin": "internal", "captured_by": "Ana Martínez", "updated_by": null, "created_at": "2026-06-16T10:05:00.000Z", "updated_at": "2026-06-16T10:05:00.000Z" } ] } ``` ## Campos de cada fila [#campos-de-cada-fila] | Campo | Tipo | Descripcion | | ----------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `date` | string (`YYYY-MM-DD`) | Día de la incidencia. Siempre dentro del periodo. | | `employee_number` | integer | Número del empleado. | | `employee_name` | string | Nombre completo del empleado. | | `incidence_code` | string | Código del tipo de incidencia. | | `incidence_name` | string | Nombre del tipo de incidencia. | | `unit` | `"days"` \| `"hours"` \| `"count"` | Unidad del tipo de incidencia. | | `quantity` | number | Cantidad registrada. Los tipos `days` registran `1`, salvo las vacaciones de medio día, que registran `0.5`. | | `notes` | string \| null | Notas de la captura. | | `approval_status` | `"approved"` \| `"pending"` \| `"rejected"` | Estatus de autorización. Las horas extra dobles y triples quedan en `pending` hasta que las autorizan todos los niveles configurados, y en `rejected` si alguno las rechaza; la nómina solo paga las `approved`. Los demás tipos siempre son `approved`. | | `source` | string | Origen exacto de la captura: `attendance_module`, `payroll_run`, `employee_portal`, `api` o `time_clock`. | | `origin` | `"internal"` \| `"external"` | Clasificación del origen: `internal` cuando la capturó el equipo de nómina en la plataforma; `external` en cualquier otro caso (portal de colaborador, reloj checador, integraciones). | | `captured_by` | string \| null | Nombre de quien registró la incidencia: una persona, la integración que la envió o la plataforma. `null` si ese autor ya no existe. | | `updated_by` | string \| null | Nombre de quien la modificó por última vez. `null` si nunca se ha editado. | Son las mismas incidencias del [registro global](/docs/developers/api-reference/attendance/list), acotadas a la ventana de fechas del periodo — no hay un registro aparte por periodo. ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------- | | `400` | `payroll_calendar_number` o `period_number` inválidos, o filtro inválido. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Listar periodos de nómina**. | | `404` | El calendario o el periodo no existen en esta organización. | # Listar periodos (/docs/developers/api-reference/payroll-calculation/list-periods) ## Descripcion general [#descripcion-general] Este endpoint devuelve los **periodos** de un calendario de nómina, con el estado de cada uno y el resultado de su corrida cuando ya fue calculada. Es el punto de partida del flujo de cálculo: aquí identificas qué periodo vas a correr, recalcular, exportar o cerrar. Los periodos los genera automáticamente el calendario al crearse (por ejemplo, 24 periodos para un calendario quincenal). No se crean ni se eliminan por API. Requiere que la API key tenga el permiso **Listar periodos de nómina**. ## Estados de un periodo [#estados-de-un-periodo] El estado no se guarda en el periodo: se **deriva** de su corrida. | Estado | Significado | | ------------ | -------------------------------------------------------------------- | | `unopened` | El periodo existe pero nunca se ha abierto. Aún no tiene corrida. | | `draft` | Tiene una corrida abierta que todavía no se calcula. | | `calculated` | Ya tiene resultados. Se puede recalcular, exportar o cerrar. | | `closed` | Periodo cerrado. Es el estado final: la API no expone la reapertura. | ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}/periods` **Path param:** `payroll_calendar_number` — entero positivo, el número del calendario dentro de la organización. Lo obtienes de [Calendarios de nómina → Listar](/docs/developers/api-reference/payroll-calendars/list). ## Consulta los periodos de un calendario [#consulta-los-periodos-de-un-calendario] *** Devuelve todos los periodos del calendario, paginados de 20 en 20 y ordenados por año fiscal y número de periodo. Un calendario semanal tiene 52 periodos por año, así que recorre las páginas con `page`. ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | ------------- | ------------------------------------------------- | --------- | ------- | ---------------------------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `fiscal_year` | integer (`YYYY`) | No | - | Filtra por año fiscal. Útil cuando el calendario abarca más de un año. | | `status` | `unopened` \| `draft` \| `calculated` \| `closed` | No | - | Filtra por el estado derivado del periodo. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/payroll-calendars/1/periods" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 24, "page": 1, "page_size": 20, "total_pages": 2, "next": "?page=2", "previous": null, "results": [ { "period_number": "2026-1", "fiscal_year": 2026, "sequence_number": 1, "start_date": "2026-01-01T00:00:00.000Z", "end_date": "2026-01-15T00:00:00.000Z", "pay_date": "2026-01-20T00:00:00.000Z", "days_in_period": 15, "accrual_month": 1, "status": "closed", "payroll_run_number": 1, "employee_count": 277, "net_total": "2062145.72", "calculated_at": "2026-01-16T18:30:34.193Z", "closed_at": "2026-01-16T18:51:21.898Z" }, { "period_number": "2026-2", "fiscal_year": 2026, "sequence_number": 2, "start_date": "2026-01-16T00:00:00.000Z", "end_date": "2026-01-31T00:00:00.000Z", "pay_date": "2026-02-05T00:00:00.000Z", "days_in_period": 16, "accrual_month": 1, "status": "unopened", "payroll_run_number": null, "employee_count": null, "net_total": null, "calculated_at": null, "closed_at": null } ] } ``` ## Encuentra el siguiente periodo por correr [#encuentra-el-siguiente-periodo-por-correr] *** Filtra por `status` para localizar el periodo que te interesa sin recorrer todas las páginas: `draft` para el que ya está abierto y falta calcular (al cerrar un periodo ordinario, el siguiente se abre solo si ya está generado), `unopened` para los que todavía no se abren, `calculated` para los que ya tienen resultados y aún puedes cerrar. **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/payroll-calendars/1/periods?status=unopened&fiscal_year=2026" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 11, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "period_number": "2026-14", "fiscal_year": 2026, "sequence_number": 14, "start_date": "2026-07-16T00:00:00.000Z", "end_date": "2026-07-31T00:00:00.000Z", "pay_date": "2026-08-05T00:00:00.000Z", "days_in_period": 16, "accrual_month": 7, "status": "unopened", "payroll_run_number": null, "employee_count": null, "net_total": null, "calculated_at": null, "closed_at": null } ] } ``` ## Campos de cada fila [#campos-de-cada-fila] | Campo | Tipo | Descripcion | | -------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `period_number` | string | Identificador del periodo dentro del calendario, con el formato `{fiscal_year}-{sequence_number}` (por ejemplo `2026-14`). Es el que usas en la URL de los demás endpoints. | | `fiscal_year` | integer | Año fiscal del periodo. | | `sequence_number` | integer | Número de periodo dentro del año (1 = primer periodo). | | `start_date` | string (fecha) | Inicio del periodo. | | `end_date` | string (fecha) | Fin del periodo. | | `pay_date` | string (fecha) | Fecha de pago. | | `days_in_period` | integer | Días que abarca el periodo. | | `accrual_month` | integer | Mes de acumulación del periodo, de `1` a `12`: el mes de la ventana mensual de ISR en que cuenta. Los periodos que nunca tuvieron un mes asignado devuelven el mes de su fecha de fin. | | `status` | string | Estado derivado: `unopened`, `draft`, `calculated` o `closed`. | | `payroll_run_number` | integer \| null | Número de corrida. `null` si el periodo no se ha abierto. | | `employee_count` | integer \| null | Empleados incluidos en la corrida. `null` si no se ha abierto. | | `net_total` | string \| null | Neto a depositar de la corrida. `null` mientras no esté calculada. | | `calculated_at` | string (fecha) \| null | Última vez que se calculó. | | `closed_at` | string (fecha) \| null | Fecha de cierre. | Los importes se devuelven como **string** para no perder precisión decimal. ## Errores [#errores] | Status | Causa | | ------ | --------------------------------------------------------------------------------------------------- | | `400` | `payroll_calendar_number` inválido en la URL, o valor inválido en `page`, `fiscal_year` o `status`. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Listar periodos de nómina**. | | `404` | El calendario no existe en esta organización. | # Recalcular (/docs/developers/api-reference/payroll-calculation/recalculate) ## Descripcion general [#descripcion-general] Este endpoint es el **recálculo maestro**: toma los cambios de **plantilla** ocurridos después de abrir el periodo, los incorpora a la corrida y recalcula una sola vez. La asistencia **no** necesita un paso de sincronización: el cálculo lee el registro global **en vivo**, así que cualquier incidencia capturada ya se toma en cuenta al recalcular. Lo que sí hay que aplicar son los cambios de plantilla: empleados **agregados**, **eliminados** y **modificados** (salario, contrato, calendario) desde que se abrió el periodo: 1. Aplica los cambios de plantilla. 2. Ejecuta el cálculo una vez, leyendo el registro de asistencia vigente. Solo aplica a periodos en estado `calculated`. No lleva cuerpo. Requiere que la API key tenga el permiso **Calcular nómina**. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}/periods/{period_number}/recalculate` **Path params:** * `payroll_calendar_number` — entero positivo, el número del calendario. * `period_number` — el periodo, con el formato `{fiscal_year}-{sequence_number}` (por ejemplo `2026-14`). ## Recalcula con los cambios pendientes [#recalcula-con-los-cambios-pendientes] *** La respuesta te dice **qué se aplicó**: cuántos empleados se modificaron, se agregaron o se quitaron de la corrida. ## Campos de la respuesta [#campos-de-la-respuesta] | Campo | Tipo | Descripcion | | -------------------- | ------- | --------------------------------------------------------------- | | `period_number` | string | El periodo recalculado. | | `payroll_run_number` | integer | Número de la corrida. | | `status` | string | Siempre `calculated` cuando la llamada tiene éxito. | | `entries_count` | integer | Empleados calculados tras aplicar los cambios. | | `employee_changes` | objeto | Cambios de plantilla aplicados: `modified`, `added`, `removed`. | Si no había nada pendiente, los contadores llegan en `0`: la corrida simplemente se recalcula, leyendo la asistencia vigente. **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/payroll-calendars/1/periods/2026-14/recalculate" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "period_number": "2026-14", "payroll_run_number": 14, "status": "calculated", "entries_count": 279, "employee_changes": { "modified": 3, "added": 2, "removed": 0 } } ``` ## Errores [#errores] | Status | Código | Causa | | ------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | — | `payroll_calendar_number` o `period_number` inválidos en la URL. | | `401` | — | API key ausente o inválida. | | `403` | — | La API key no tiene el permiso **Calcular nómina**. | | `404` | — | El calendario o el periodo no existen en esta organización. | | `409` | `NOT_CALCULATED` | El periodo no está calculado. Córrelo primero con [calcular](/docs/developers/api-reference/payroll-calculation/calculate). | | `409` | `CLOSED` | El periodo está cerrado y no admite recálculos. | | `409` | `CANCELLED` | La corrida del periodo fue cancelada y no admite recálculos. | | `409` | `NO_EMPLOYEES` | Las bajas pendientes dejarían la corrida sin empleados. | | `409` | `OUT_OF_NORM` | Alguno de los empleados agregados o modificados tiene un contrato con prestaciones **por debajo del mínimo de ley**. La respuesta incluye `norm_violations`. | | `409` | `CATALOG_MISSING` | Falta configurar un catálogo fiscal requerido para el periodo. | | `409` | `NO_SNAPSHOT` | La corrida no tiene una configuración congelada, así que no puede recalcularse. | | `409` | `RETRO_STATE_CHANGED` | Un ajuste retroactivo cambió durante el cálculo. Vuelve a intentar. | | `409` | `HOUSING_INSURANCE_STATE_CHANGED` | Otra nómina del bimestre cambió el seguro de vivienda INFONAVIT durante el cálculo. Vuelve a intentar. | | `409` | `CONCURRENT_CALCULATION` | Otro cálculo de nómina de los mismos empleados está en curso. Vuelve a intentar en un momento. | | `409` | `NOT_ONE_EMPLOYMENT` | La corrida es un finiquito con más de un empleado; un finiquito cubre a uno solo. | | `409` | `NOT_CLOSING_PERIOD` | La corrida tiene activado el ajuste anual de ISR, pero no es el último periodo ordinario del ejercicio. Desactívalo en las opciones de la corrida. | | `409` | `NO_ANNUAL_ISR_TARIFF` | La corrida aplica el ajuste anual de ISR, no está configurada la tarifa anual de ISR del ejercicio y al menos un empleado no está excluido por tipo de trabajador, no estar marcado para el ajuste, año incompleto o solicitud por escrito. | Las validaciones se ejecutan **antes** de tocar la corrida: si alguna falla, la corrida queda intacta (nunca a medio aplicar). # Timbrar (/docs/developers/api-reference/payroll-calculation/stamp) ## Descripcion general [#descripcion-general] Este endpoint **timbra** ante el SAT (CFDI de nómina) los recibos de un periodo cerrado. Cerrar el periodo no timbra nada: el timbrado es un paso aparte. Cada llamada crea un **lote de timbrado** con los recibos que aún no se enviaron y responde de inmediato; se timbran en segundo plano. Un recibo con error se reintenta desde la bandeja de timbrado. Todos los recibos del lote llevan la **fecha de pago** que envías. Si no la envías, se toma la fecha de pago del periodo. Requiere que la API key tenga el permiso **Timbrar nómina**. Las API keys con **Cerrar nómina** lo recibieron al agregarse este endpoint. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/payroll-calendars/{payroll_calendar_number}/periods/{period_number}/stamp` **Path params:** * `payroll_calendar_number`: entero positivo, el número del calendario. * `period_number`: el periodo, con el formato `{fiscal_year}-{sequence_number}` (por ejemplo `2026-14`). **Body (opcional):** * `payment_date`: fecha de pago de los recibos, con el formato `YYYY-MM-DD`. ## Inicia el timbrado del periodo [#inicia-el-timbrado-del-periodo] *** Crea el lote de timbrado con los recibos pendientes del periodo y los envía al SAT en segundo plano. ## Campos de la respuesta [#campos-de-la-respuesta] | Campo | Tipo | Descripcion | | -------------------- | -------------- | ------------------------------------- | | `period_number` | string | El periodo timbrado. | | `payroll_run_number` | integer | Número de la corrida. | | `stamping_batch_id` | string | Identificador del lote de timbrado. | | `payment_date` | string (fecha) | Fecha de pago que llevan los recibos. | | `receipt_count` | integer | Recibos incluidos en el lote. | **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/payroll-calendars/1/periods/2026-14/stamp" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payment_date": "2026-07-31" }' ``` **Respuesta** ```json { "period_number": "2026-14", "payroll_run_number": 14, "stamping_batch_id": "6f1c2a8e-3b0d-4c55-9a51-2f7d0e9b4c11", "payment_date": "2026-07-31", "receipt_count": 48 } ``` ## Errores [#errores] | Status | Código | Causa | | ------ | ------------------ | -------------------------------------------------------------------------------------------------------------------- | | `400` | | `payroll_calendar_number` o `period_number` inválidos en la URL, o `payment_date` no es una fecha válida. | | `401` | | API key ausente o inválida. | | `403` | | La API key no tiene el permiso **Timbrar nómina**, o el periodo es de una razón social fuera de su alcance. | | `404` | | El calendario o el periodo no existen en esta organización. | | `409` | `NOT_CALCULATED` | El periodo no está calculado (o nunca se abrió). | | `409` | `NOT_CLOSED` | El periodo no está cerrado. Ciérralo primero con [cerrar](/docs/developers/api-reference/payroll-calculation/close). | | `409` | `NOTHING_TO_STAMP` | Todos los recibos del periodo ya se enviaron a timbrar. | # Eliminar (/docs/developers/api-reference/payroll-concepts/delete) ## Descripcion general [#descripcion-general] Esta ruta realiza el **borrado** de un concepto de nómina **extralegal** (`origin: "custom"`): a partir de ese momento se trata como no encontrado en `PATCH`/`DELETE` (`404`). Los conceptos de ley no se eliminan por la API. El borrado **no procede** si el concepto tiene importes capturados o créditos de empleado activos; en esos casos desactívalo con `PATCH is_active=false`. La API key necesita el permiso **Eliminar conceptos de nómina** y alcance sobre toda la organización. ## Ruta [#ruta] `DELETE https://app.integranom.com/api/v1/payroll-concepts/{payroll_concept_number}` **Parámetro de ruta:** `payroll_concept_number`: entero positivo, el número del concepto dentro de la organización. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/payroll-concepts/42" \ -H "Authorization: Bearer $API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] 1. **Rechaza** la solicitud (`403`) si el concepto es de ley. 2. **Rechaza** la solicitud (`400`) si es **Desc Patronal Fondo Ahorro** o **Vales de despensa entregados**: descuentan lo que pagan **Desc Aportación Trabajador F.A** y **Vales de despensa**, así que nunca se eliminan. Mientras ese concepto está activo, el código es `SAVINGS_FUND_PAIR_ACTIVE` o `FOOD_VOUCHER_PAIR_ACTIVE`; si está inactivo, `PAIRED_DEDUCTION_NOT_DELETABLE`. 3. **Rechaza** la solicitud (`400`) si el concepto tiene importes capturados o créditos de empleado activos. 4. **Marca** el concepto como eliminado e inactivo. ### `400`: deducción que descuenta otro concepto activo [#400-deducción-que-descuenta-otro-concepto-activo] ```json { "message": "Vales de despensa entregados no se puede eliminar mientras Vales de despensa esté activo.", "code": "FOOD_VOUCHER_PAIR_ACTIVE" } ``` ### `400`: deducción que descuenta otro concepto inactivo [#400-deducción-que-descuenta-otro-concepto-inactivo] ```json { "message": "Vales de despensa entregados no se puede eliminar porque descuenta lo que paga Vales de despensa.", "code": "PAIRED_DEDUCTION_NOT_DELETABLE" } ``` ## Errores [#errores] | Status | Causa | | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `payroll_concept_number` inválido en la URL. | | `400` | Es **Desc Patronal Fondo Ahorro** con **Desc Aportación Trabajador F.A** activo (`SAVINGS_FUND_PAIR_ACTIVE`), o **Vales de despensa entregados** con **Vales de despensa** activo (`FOOD_VOUCHER_PAIR_ACTIVE`). | | `400` | Es **Desc Patronal Fondo Ahorro** o **Vales de despensa entregados** y su concepto pareado está inactivo (`PAIRED_DEDUCTION_NOT_DELETABLE`). | | `400` | El concepto tiene importes capturados o créditos de empleado activos. | | `403` | El concepto es de ley, o la API key no tiene alcance sobre toda la organización. | | `404` | El concepto no existe o ya fue eliminado en esta organización. | # Editar (/docs/developers/api-reference/payroll-concepts/edit) ## Descripcion general [#descripcion-general] Esta ruta actualiza parcialmente un concepto de nómina **extralegal** (`origin: "custom"`). Todos los campos del cuerpo son **opcionales**: si omites una clave, ese campo queda intacto. El identificador en la URL es el `payroll_concept_number` del concepto dentro de tu organización. Los conceptos de ley (`origin: "legal"`) los administra la plataforma y no se editan por la API. Una solicitud exitosa devuelve `200` con la forma completa del concepto actualizado. La API key necesita el permiso **Actualizar conceptos de nómina** y alcance sobre toda la organización. ## Ruta [#ruta] `PATCH https://app.integranom.com/api/v1/payroll-concepts/{payroll_concept_number}` **Parámetro de ruta:** `payroll_concept_number`: entero positivo, el número del concepto dentro de la organización. ## Actualizacion minima [#actualizacion-minima] *** Envía solo los campos que quieras cambiar. El resto del concepto queda intacto. ```json { "name": "Bono de productividad", "sort_order": 15 } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/payroll-concepts/42" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Bono de productividad", "sort_order": 15 }' ``` **Respuesta** ```json { "payroll_concept_number": 42, "code": "P070", "name": "Bono de productividad", "kind": "perception", "informative_subtype": null, "sat_classification": "percepcion", "sat_key": "038", "behavior": "captured", "calculator_key": null, "calc_mode": "manual", "formula_expression": null, "capture_unit": "amount", "receipt_visibility": "when_nonzero", "sdi_variability": "variable", "deduction_priority": null, "tracks_balance": false, "origin": "custom", "template_key": null, "is_mandatory": false, "self_managed": false, "employer_responsibility": false, "affects_net_cash": true, "reduces_isr_base": false, "reduces_isn_base": false, "is_active": true, "sort_order": 15, "created_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-15T11:15:32.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | ------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `code` | string | 1–60 caracteres. Único por organización. | | `name` | string | No puede ir vacío. | | `sat_key` | string \| null | Clave del catálogo SAT de la clasificación del concepto. `null` o `""` la limpia, salvo en un concepto que se timbra. Un concepto que no se timbra no lleva clave. | | `formula_expression` | string \| null | Solo para conceptos de fórmula (`behavior: "formula"`); no se puede limpiar. En cualquier otro concepto se ignora. | | `employer_responsibility` | boolean | | | `affects_net_cash` | boolean | | | `reduces_isr_base` | boolean | Solo deducciones con fórmula o captura manual. `true` resta el importe de la base gravable de ISR antes de calcular el impuesto. En otro concepto responde `400`. | | `reduces_isn_base` | boolean | Solo deducciones con fórmula o captura manual. `true` resta el importe de la base del impuesto sobre nómina. En otro concepto responde `400`. | | `is_active` | boolean | `false` desactiva el concepto. **Desc Patronal Fondo Ahorro** no se puede desactivar mientras **Desc Aportación Trabajador F.A** esté activo, ni **Vales de despensa entregados** mientras **Vales de despensa** esté activo. `true` sobre **Desc Aportación Trabajador F.A** o **Vales de despensa** también activa su deducción si estaba inactiva. | | `sort_order` | entero | Mayor o igual a 0. | ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK`: el objeto completo del concepto actualizado, con la misma forma del ejemplo. `is_mandatory` es de solo lectura y no se envía en el cuerpo: es `true` solo en un concepto de ley que la nómina calcula a cada empleado, como el sueldo o el ISR, y `false` en los demás, incluidos todos los extralegales. Quién recibe un concepto extralegal lo decide su contrato. ### `400`: Desc Patronal Fondo Ahorro con el fondo del trabajador activo [#400-desc-patronal-fondo-ahorro-con-el-fondo-del-trabajador-activo] ```json { "message": "La validación falló.", "errors": [ { "field": "is_active", "code": "SAVINGS_FUND_PAIR_ACTIVE", "message": "Desc Patronal Fondo Ahorro no se puede desactivar mientras Desc Aportación Trabajador F.A esté activo." } ] } ``` Con **Vales de despensa entregados** mientras **Vales de despensa** está activo, el error es el mismo con `code` `FOOD_VOUCHER_PAIR_ACTIVE` y el mensaje "Vales de despensa entregados no se puede desactivar mientras Vales de despensa esté activo." ## Errores [#errores] | Status | Causa | | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `payroll_concept_number` inválido en la URL, cuerpo que no es JSON, o falla de validación de campos. | | `400` | `sat_key` inválida para la clasificación del concepto, limpiada en un concepto que se timbra, o enviada a un concepto que no se timbra (`INVALID_INPUT`). | | `400` | `formula_expression` en `null` en un concepto de fórmula. | | `400` | `is_active: false` sobre **Desc Patronal Fondo Ahorro** mientras **Desc Aportación Trabajador F.A** está activo (`SAVINGS_FUND_PAIR_ACTIVE`). | | `400` | `is_active: false` sobre **Vales de despensa entregados** mientras **Vales de despensa** está activo (`FOOD_VOUCHER_PAIR_ACTIVE`). | | `400` | `reduces_isr_base` o `reduces_isn_base` en `true` sobre un concepto que no es deducción con fórmula o captura manual (`INVALID_INPUT`). | | `403` | El concepto es de ley (`IMMUTABLE_LEGAL_CONCEPT`), o la API key no tiene alcance sobre toda la organización. | | `404` | El concepto no existe o fue eliminado en esta organización. | | `409` | Ya existe otro concepto con ese `code` en la organización. | # Detalle (/docs/developers/api-reference/platform-catalogs/detail) ## Descripcion general [#descripcion-general] Este endpoint devuelve el detalle de un catálogo de plataforma, incluido su **valor efectivo** (el que aplica hoy) y el **valor base de la plataforma** (para comparar). El identificador en la URL es el `catalog_key` del catálogo. El valor efectivo se resuelve con la precedencia razón social → organización → adopción anticipada de la razón social → plataforma: si tu organización tiene un override activo, se refleja aquí; de lo contrario, se utiliza el valor de la plataforma. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/platform-catalogs/{catalog_key}` **Path param:** `catalog_key` — string, la clave del catálogo (por ejemplo `uma`, `isr_tariff`, `imss_worker_ramos`). ## Consulta un catálogo [#consulta-un-catálogo] *** Devuelve el detalle del catálogo indicado en `catalog_key`. El campo `source` puede ser `"rs"` (override por razón social), `"org"` (override de la organización) o `"platform"` (valor base de la plataforma). El `effective_payload` es el valor que aplica hoy con la precedencia RS → organización → plataforma, mientras que `platform_payload` es el valor de la plataforma que aplica hoy, útil para comparar. La forma de ambos depende del `shape` del catálogo (`scalar`, `range_table` o `keyed_table`). **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/platform-catalogs/uma" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "key": "uma", "name": "UMA", "description": "Unidad de Medida y Actualización (diaria, mensual, anual).", "shape": "scalar", "unit": "MXN", "group_key": "parametros_generales", "group_label": "Parámetros generales", "frequency_scoped": false, "frequency": null, "is_federal": false, "regulatory": "legal", "lookup_mode": null, "input_var": null, "legal_entity_number": null, "source": "platform", "is_stale": false, "pending_approval": false, "effective_payload": { "...": "estructura según el shape del catálogo" }, "platform_payload": { "...": "el valor sugerido por la plataforma" } } ``` ## Lectura con frequency y alcance por razón social [#lectura-con-frequency-y-alcance-por-razón-social] *** Para un catálogo frequency-scoped, indica la `frequency` que quieras consultar. Con `legal_entity_number` acotas la lectura a una razón social específica: si esa razón social tiene un override, el `source` regresa como `"rs"` y `effective_payload` refleja su valor. Si esa razón social adelantó una versión publicada de la plataforma y ya llegó la fecha que eligió, `effective_payload` trae esa versión y el `source` sigue siendo `"platform"`. ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | --------------------- | ------- | --------- | ------------- | -------------------------------------------------------------------------------------------------------------------------- | | `frequency` | string | No | `semimonthly` | Solo aplica a catálogos `frequency_scoped` (como `isr_tariff`). Si se omite en uno frequency-scoped, se usa `semimonthly`. | | `legal_entity_number` | integer | No | - | Lectura con alcance por razón social. Debe existir en tu organización. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/platform-catalogs/isr_tariff?frequency=monthly&legal_entity_number=1" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "key": "isr_tariff", "name": "Tarifa ISR", "description": "Tarifa del ISR por periodicidad (Art. 96 LISR).", "shape": "range_table", "unit": "MXN", "group_key": "isr", "group_label": "ISR e impuesto sobre nómina", "frequency_scoped": true, "frequency": "monthly", "is_federal": true, "regulatory": "legal", "lookup_mode": null, "input_var": null, "legal_entity_number": 1, "source": "rs", "is_stale": true, "pending_approval": false, "effective_payload": { "...": "estructura según el shape del catálogo" }, "platform_payload": { "...": "el valor sugerido por la plataforma" } } ``` ## Catálogo isn\_by\_state [#catálogo-isn_by_state] *** En `isn_by_state` (`keyed_table`) la clave de cada entrada es el código de la entidad federativa. En `effective_payload` y `platform_payload` cada valor es siempre una **cadena decimal** con la tasa base del estado: la tasa plana, o la del primer rango cuando el estado cobra una tarifa progresiva. Los estados que tienen una definición publicada la traen además en dos campos aparte, `isn_definitions` (la que aplica hoy) y `platform_isn_definitions` (la de la plataforma). Cada definición es un objeto con **exactamente uno** de `rate` (cadena decimal, tasa plana) o `brackets` (tarifa progresiva), más los campos opcionales `taxSurchargeRate` (fracción del impuesto que se suma como sobretasa), `baseSurchargeRate` (fracción de la nómina que se suma como sobretasa) y `exemptMonthlyAmount` (monto mensual de la nómina que no se grava). Todos son cadenas decimales. Cada elemento de `brackets` trae `lowerLimit`, `upperLimit` (`null` en el último), `fixedFee` y `marginalRate`. Los rangos van en orden ascendente, contiguos y sin traslape, y solo el último puede quedar abierto. En la tarifa progresiva, los límites son del total mensual de la nómina del estado. ```json { "effective_payload": { "SIN": "0.024", "CAM": "0.03", "QUE": "0.03", "JAL": "0.03" }, "isn_definitions": { "SIN": { "brackets": [ { "lowerLimit": "0.01", "upperLimit": "500000.00", "fixedFee": "0.00", "marginalRate": "0.024" }, { "lowerLimit": "500000.01", "upperLimit": "700000.00", "fixedFee": "12000.00", "marginalRate": "0.026" }, { "lowerLimit": "700000.01", "upperLimit": "900000.00", "fixedFee": "17200.00", "marginalRate": "0.028" }, { "lowerLimit": "900000.01", "upperLimit": null, "fixedFee": "22800.00", "marginalRate": "0.030" } ] }, "CAM": { "rate": "0.03", "taxSurchargeRate": "0.33" }, "QUE": { "rate": "0.03", "exemptMonthlyAmount": "76617.76" } } } ``` ## Campos de la respuesta [#campos-de-la-respuesta] | Campo | Tipo | Notas | | --------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `key` | string | Clave del catálogo. | | `name` | string | Nombre legible. | | `description` | string | Descripción del catálogo. | | `shape` | `"scalar"` \| `"range_table"` \| `"keyed_table"` | Forma del valor. Determina la estructura de `effective_payload` y `platform_payload`. | | `unit` | string | Unidad del valor (por ejemplo `MXN`). | | `group_key` / `group_label` | string | Grupo del catálogo (clave y etiqueta). | | `frequency_scoped` | boolean | `true` si el catálogo depende de una periodicidad. | | `frequency` | string \| null | Periodicidad resuelta para esta lectura; `null` si el catálogo no es frequency-scoped. | | `is_federal` | boolean | `true` si es tabla federal. | | `regulatory` | `"legal"` \| `"platform_default"` | Naturaleza regulatoria del catálogo. | | `legal_entity_number` | integer \| null | Razón social del alcance de la lectura; `null` si es a nivel organización o plataforma. | | `source` | `"rs"` \| `"org"` \| `"platform"` | Origen del valor efectivo, según la precedencia RS → organización → plataforma. | | `is_stale` | boolean | `true` cuando la plataforma publicó una versión más reciente que la última revisión de tu override — señal de que tu valor autoadministrado quedó rezagado respecto al de la plataforma. | | `pending_approval` | boolean | `true` cuando existe un override federal que aún espera la aprobación de un segundo administrador. Mientras tanto el resolver lo ignora, por lo que `source` se reporta como `"platform"` aunque este campo sea `true`. (Los overrides creados por la API nunca quedan pendientes.) | | `effective_payload` | object | Valor que aplica hoy. Su forma depende del `shape` del catálogo. | | `platform_payload` | object | Valor base de la plataforma, para comparar. Su forma depende del `shape` del catálogo. | | `isn_definitions` | object | Solo en `isn_by_state`: las definiciones con tarifa, sobretasa o monto exento que aplican hoy, por estado. | | `platform_isn_definitions` | object | Solo en `isn_by_state`: las definiciones de la plataforma, por estado. | ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------ | | `400` | `frequency` inválida, o `legal_entity_number` inválido / que no existe en tu organización. | | `401` | API key ausente o inválida. | | `404` | El catálogo con ese `catalog_key` no existe. | # Editar (/docs/developers/api-reference/platform-catalogs/edit) ## Descripcion general [#descripcion-general] Este endpoint escribe un **override** sobre un catálogo de plataforma, a nivel de toda la organización o acotado a una razón social. El override **toma efecto de inmediato** para cualquier catálogo, incluidas las tablas federales (UMA, ISR, IMSS). El identificador en la URL es el `catalog_key` del catálogo. Una solicitud exitosa devuelve `200` con la clave, el origen resultante y el `effective_payload` ya en vigor. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/platform-catalogs/{catalog_key}` **Path param:** `catalog_key` — string, la clave del catálogo a autoadministrar (por ejemplo `uma`, `isr_tariff`, `imss_worker_ramos`). ## Override a nivel organización [#override-a-nivel-organización] *** Omite `legal_entity_number` (o envíalo como `null`) para que el override aplique a **toda la organización**. El `payload` es el nuevo valor y se valida contra el `shape` del catálogo. Su estructura **varía por catálogo**: el ejemplo de abajo (`scalar`) es ilustrativo; la forma real depende del `shape` (`scalar` / `range_table` / `keyed_table`). Para conocer la estructura exacta del `payload`, consulta primero `effective_payload` / `platform_payload` en el endpoint de [detalle](/docs/developers/api-reference/platform-catalogs/detail). ```json { "payload": { "daily": "113.14", "monthly": "3439.46" } } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/platform-catalogs/uma" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "payload": { "daily": "113.14", "monthly": "3439.46" } }' ``` **Respuesta** ```json { "key": "uma", "source": "org", "frequency": null, "legal_entity_number": null, "effective_payload": { "daily": "113.14", "monthly": "3439.46" } } ``` ## Override frequency-scoped y por razón social [#override-frequency-scoped-y-por-razón-social] *** Para un catálogo `frequency_scoped` (como `isr_tariff`) el campo `frequency` es **requerido**; en el resto de los catálogos se ignora. Con `legal_entity_number` limitas el override a esa razón social (el `source` regresa como `"rs"`); si lo omites, aplica a toda la organización. El campo `reason` es opcional e informativo. Recuerda que el `payload` de este ejemplo es ilustrativo: su forma real depende del `shape` del catálogo. **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/platform-catalogs/isr_tariff" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "frequency": "monthly", "legal_entity_number": 1, "reason": "Actualización interna de tarifa", "payload": { "...": "estructura según el shape del catálogo" } }' ``` **Respuesta** ```json { "key": "isr_tariff", "source": "rs", "frequency": "monthly", "legal_entity_number": 1, "effective_payload": { "...": "estructura según el shape del catálogo" } } ``` ## Payload de isn\_by\_state [#payload-de-isn_by_state] *** Para `isn_by_state` el `payload` es un objeto que va de la clave del estado a su valor, y cada valor es una cadena decimal (tasa plana) o un objeto con **exactamente uno** de `rate` o `brackets`, más los opcionales `taxSurchargeRate`, `baseSurchargeRate` y `exemptMonthlyAmount`, todos cadenas decimales. Los `brackets` llevan `lowerLimit`, `upperLimit` (`null` en el último), `fixedFee` y `marginalRate`. En la respuesta, `effective_payload` trae cada estado como una cadena decimal con su tasa base, y las definiciones con objeto vienen aparte en `isn_definitions`. ```json { "payload": { "SIN": { "brackets": [ { "lowerLimit": "0.01", "upperLimit": "500000.00", "fixedFee": "0.00", "marginalRate": "0.024" }, { "lowerLimit": "500000.01", "upperLimit": null, "fixedFee": "12000.00", "marginalRate": "0.026" } ] }, "CAM": { "rate": "0.03", "taxSurchargeRate": "0.33" }, "QUE": { "rate": "0.03", "exemptMonthlyAmount": "76617.76" }, "JAL": "0.03" } } ``` El `payload` se rechaza con `400` cuando un estado trae `rate` y `brackets` a la vez o ninguno de los dos, cuando un valor no es una cadena decimal válida, cuando trae un campo que no existe, o cuando los rangos no son contiguos y ascendentes, se traslapan o dejan abierto un rango que no es el último. El cuerpo de la respuesta es `{ "message": "Invalid catalog payload for this catalog's shape.", "errors": { ... } }`, con `errors` indexado por la clave del estado que falló. ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | --------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `frequency` | string | **Requerido solo** para catálogos `frequency_scoped` (por ejemplo `isr_tariff`). Se ignora en el resto. | | `legal_entity_number` | integer \| null | Omitir o `null` = override a nivel de toda la organización. Un número lo limita a esa razón social. | | `reason` | string | Opcional e informativo. | | `payload` | object | El nuevo valor. Se valida contra el `shape` del catálogo; su estructura varía por catálogo. Consulta `effective_payload` / `platform_payload` en el [detalle](/docs/developers/api-reference/platform-catalogs/detail) para conocer la forma exacta. | ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — la clave del catálogo, el origen resultante y el valor efectivo ya en vigor: ```json { "key": "uma", "source": "org", "frequency": null, "legal_entity_number": null, "effective_payload": { "daily": "113.14", "monthly": "3439.46" } } ``` ## Errores [#errores] | Status | Código | Causa | | ------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `FREQUENCY_REQUIRED` | Catálogo frequency-scoped sin `frequency`. | | `400` | `INVALID_PAYLOAD` | El `payload` no cumple el shape del catálogo. El cuerpo de la respuesta trae `errors` con el detalle. | | `400` | `LEGAL_ENTITY_NOT_FOUND` | La razón social indicada no existe en tu organización. | | `400` | `BELOW_MINIMUM` | Un valor queda por debajo del mínimo del catálogo. En `factor_dias_mes`: 5 para `semana`, 12 para `quincena`, 28 para `mes` y 336 para `anio`. | | `400` | `ABOVE_MAXIMUM` | Un valor queda por encima del máximo del catálogo. En `factor_dias_mes`, `mes` no puede pasar de 31. | | `401` | - | API key ausente o inválida. | | `404` | - | El catálogo con ese `catalog_key` no existe. | ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] | Código | Significado | | ------------------------ | --------------------------------------------------------------------------------------------------- | | `FREQUENCY_REQUIRED` | El catálogo es frequency-scoped y no incluiste `frequency` en el cuerpo. | | `INVALID_PAYLOAD` | El `payload` no cumple el `shape` del catálogo. Revisa `errors` en la respuesta para ver qué falló. | | `LEGAL_ENTITY_NOT_FOUND` | El `legal_entity_number` no corresponde a ninguna razón social de tu organización. | | `BELOW_MINIMUM` | Un valor del `payload` es menor al mínimo que permite el catálogo. | | `ABOVE_MAXIMUM` | Un valor del `payload` es mayor al máximo que permite el catálogo. | # Listar (/docs/developers/api-reference/platform-catalogs/list) ## Descripcion general [#descripcion-general] Este endpoint devuelve el registro completo de catálogos de plataforma disponibles para tu organización. Los catálogos de plataforma son tablas de parámetros fiscales que la plataforma mantiene (UMA, tarifa ISR, cuotas IMSS, INFONAVIT, ISN por estado, etc.). No se crean ni se borran: solo se consultan (`GET`) y, si lo necesitas, se autoadministran con un override de la organización (`PATCH`). El conjunto de catálogos es fijo, por lo que la respuesta **no se pagina**: devuelve todas las filas de una sola vez. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/platform-catalogs` ## Consulta el registro de catálogos [#consulta-el-registro-de-catálogos] *** La respuesta trae `count` con el número total de catálogos y `results` con una fila por cada catálogo. Como el conjunto es fijo, **no hay paginación** ni parámetros de consulta: se devuelve el registro completo. ## Campos de cada fila [#campos-de-cada-fila] | Campo | Tipo | Descripcion | | ------------------ | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `key` | string | Identificador del catálogo. Es el valor que usas en la URL de los endpoints de detalle y edición. | | `name` | string | Nombre legible del catálogo. | | `description` | string | Descripción del catálogo. | | `shape` | `"scalar"` \| `"range_table"` \| `"keyed_table"` | Forma del valor del catálogo. Determina la estructura del `payload` al leerlo o editarlo. | | `unit` | string | Unidad del valor (por ejemplo `MXN`). | | `group_key` | string | Clave del grupo al que pertenece el catálogo. | | `group_label` | string | Etiqueta legible del grupo. | | `frequency_scoped` | boolean | `true` si el catálogo requiere una `frequency` al leerlo o editarlo (por ejemplo la tarifa ISR). | | `is_federal` | boolean | `true` si es una tabla federal. | | `regulatory` | `"legal"` \| `"platform_default"` | Naturaleza regulatoria del catálogo. | | `source` | `"org"` \| `"platform"` | `"org"` si tu organización lo autoadministra con un override activo; `"platform"` si usa el valor de la plataforma. | | `is_stale` | boolean | `true` cuando tienes un override pero la plataforma publicó después una versión más reciente — tu valor autoadministrado quedó rezagado. Consulta el [detalle](/docs/developers/api-reference/platform-catalogs/detail) para comparar contra `platform_payload`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/platform-catalogs" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 12, "results": [ { "key": "uma", "name": "UMA", "description": "Unidad de Medida y Actualización (diaria, mensual, anual).", "shape": "scalar", "unit": "MXN", "group_key": "parametros_generales", "group_label": "Parámetros generales", "frequency_scoped": false, "is_federal": false, "regulatory": "legal", "source": "platform", "is_stale": false }, { "key": "isr_tariff", "name": "Tarifa ISR", "description": "Tarifa del ISR por periodicidad (Art. 96 LISR).", "shape": "range_table", "unit": "MXN", "group_key": "isr", "group_label": "ISR e impuesto sobre nómina", "frequency_scoped": true, "is_federal": true, "regulatory": "legal", "source": "org", "is_stale": true } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | --------------------------- | | `401` | API key ausente o inválida. | # Registrar (/docs/developers/api-reference/time-clock-events/create) ## Descripcion general [#descripcion-general] Este endpoint recibe un **lote de checadas** desde un reloj checador o una integración. Está pensado para que el dispositivo reenvíe lotes completos sin miedo: la ingesta es **idempotente**, una misma marca (mismo empleo, misma marca de tiempo) nunca se duplica. El lote es **todo o nada**: si una sola fila es inválida o su empleado no se puede resolver, no se registra ninguna checada y la respuesta lista los problemas fila por fila. Cuando el lote entra, la plataforma vuelve a evaluar los días que tocaste y deriva **retardos**, nunca faltas. Si la organización activó **Derivar faltas** y uno de esos días ya tenía una falta derivada por no tener checadas, la checada nueva la retira. La derivación solo corre si la organización tiene activado el interruptor **Derivar retardos** en Configuración → Reloj checador, y si el empleo tiene un horario efectivo (el de su contrato, o el horario default de la organización). Requiere que la API key tenga el permiso **Registrar checadas**. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/time-clock-events` ## Registra un lote de checadas [#registra-un-lote-de-checadas] *** Hasta **100 checadas** por solicitud. El ejemplo sube la entrada y la salida de un empleado cuyo horario empieza a las 9:00 con 15 minutos de tolerancia: como llegó a las 9:20, se deriva un retardo → `derived.created: 1`. `created` son las checadas nuevas; `duplicates`, las que ya existían (o venían repetidas en el mismo lote). Reenviar el lote completo devuelve `created: 0` y no vuelve a derivar nada — la derivación anterior sigue en pie. ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Requerido | Notas | | --------------- | --------- | --------- | --------------------------------------------------------------------------------------------------- | | `events` | object\[] | Sí | 1–100 checadas. | | `confirm_retro` | boolean | No | Autoriza que los días dentro de un periodo cerrado se registren como ajuste retroactivo. Ver abajo. | ## Campos de cada checada [#campos-de-cada-checada] | Campo | Tipo | Requerido | Notas | | ----------------- | ----------------- | --------- | -------------------------------------------------------------------------------------------------------------------- | | `employee_number` | integer | Sí | Número del empleado dentro de la organización. | | `timestamp` | string (ISO 8601) | Sí | Instante de la marca, **con zona horaria** (`...Z` o `...-06:00`). Una hora local sin offset se rechaza por ambigua. | | `direction` | `"in"` \| `"out"` | No | Dirección declarada por el dispositivo. Una marca `"out"` nunca cuenta como la llegada del día. | | `device_id` | string | No | Identificador del dispositivo. Máximo 120 caracteres. | **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/time-clock-events" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "events": [ { "employee_number": 142, "timestamp": "2026-06-05T09:20:00-06:00", "direction": "in", "device_id": "RELOJ-PLANTA-1" }, { "employee_number": 142, "timestamp": "2026-06-05T18:03:00-06:00", "direction": "out", "device_id": "RELOJ-PLANTA-1" } ] }' ``` **Respuesta** ```json { "summary": { "total": 2, "created": 2, "duplicates": 0 }, "derived": { "created": 1, "updated": 0, "deleted": 0 } } ``` ## Dias dentro de un periodo cerrado (ajuste retroactivo) [#dias-dentro-de-un-periodo-cerrado-ajuste-retroactivo] *** Cerrar un periodo es definitivo. Un retardo derivado de una checada con fecha dentro de sus días no reescribe la nómina ya pagada: se registra como un **ajuste retroactivo** que corrige la diferencia en el **siguiente** periodo. Por eso, si el `local_date` de alguna checada cae en un periodo cerrado, el lote se rechaza con `409 RETRO_CONFIRMATION_REQUIRED` (con `retro_periods`, los periodos afectados) y **no se registra nada**. Reenvíalo con **`confirm_retro: true`** para aceptar. La puerta solo aplica cuando la derivación de retardos está activada: con el interruptor apagado una checada no deriva retardos y el lote entra sin confirmación. Con **Derivar faltas** encendida, una checada de un día que tenía una falta derivada la retira aunque el periodo esté cerrado; ese cambio entra solo como ajuste retroactivo, sin pedir `confirm_retro`. **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/time-clock-events" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "confirm_retro": true, "events": [ { "employee_number": 1, "timestamp": "2026-01-05T09:40:00-06:00", "direction": "in" } ] }' ``` **Respuesta** ```json { "summary": { "total": 1, "created": 1, "duplicates": 0 }, "derived": { "created": 1, "updated": 0, "deleted": 0 } } ``` ## Como se identifica al empleado [#como-se-identifica-al-empleado] Cada checada debe traer `employee_number`: el número del empleado dentro de la organización. Con él se busca su empleo activo o pendiente, y ahí aterriza la checada. Si el número no existe en la organización → `EMPLOYEE_NOT_FOUND`. Si existe pero la persona no tiene un empleo activo ni pendiente → `NO_ACTIVE_EMPLOYMENT`. Cuando alguien tiene más de un empleo abierto en la misma organización, la checada aterriza en el más reciente. ## Que se deriva (y que no) [#que-se-deriva-y-que-no] | Situación | Resultado | | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | Primera marca del día más tarde que la hora de entrada + tolerancia | **Retardo** en el registro de asistencia, con origen `time_clock`. | | Día laborable **sin** checadas | Nada al recibir el lote. Con **Derivar faltas** encendida, un proceso nocturno lo registra como falta con origen `time_clock` cuando el día termina. | | Día con una falta derivada que recibe su primera checada | La falta derivada se elimina. | | Primera marca del día en o después de la hora de salida | Nada — se asume una salida sin registrar del día anterior. | | El día ya tiene una incidencia capturada a mano | Nada. Lo manual siempre gana; el motor no pisa lo que RH ya registró. | | Turno nocturno: llegada después de medianoche | Nada. Esa marca cae en el día natural siguiente y no se compara contra la entrada del turno. | | El empleo no tiene horario efectivo | Nada. | ## Forma de la respuesta [#forma-de-la-respuesta] `201` — el lote se registró. | Campo | Significado | | -------------------- | --------------------------------------------------------- | | `summary.total` | Checadas enviadas en la solicitud. | | `summary.created` | Checadas nuevas registradas. | | `summary.duplicates` | Checadas que ya existían o venían repetidas en el lote. | | `derived.created` | Retardos derivados nuevos en el registro de asistencia. | | `derived.updated` | Retardos derivados cuya cantidad cambió. | | `derived.deleted` | Retardos derivados que dejaron de aplicar y se retiraron. | ## Errores [#errores] | Status | Código | Causa | | ------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `400` | — | Cuerpo mal formado, o `events` vacío o con más de 100 filas. | | `400` | `INVALID_INPUT` | Alguna fila no pasó validación (`timestamp` sin zona horaria, `direction` fuera de `in`/`out`, etc.). No se registra nada. | | `400` | `EMPLOYEE_NOT_FOUND` | El `employee_number` no existe en la organización. | | `400` | `NO_ACTIVE_EMPLOYMENT` | El empleado no tiene empleo activo o pendiente. | | `401` | — | API key ausente o inválida. | | `403` | — | La API key no tiene el permiso **Registrar checadas**. | | `409` | `RETRO_CONFIRMATION_REQUIRED` | Algún día cae en un periodo cerrado. Reenvía con `confirm_retro: true`. | Los errores de fila llegan con el índice dentro de `events` y los datos de la checada, para que puedas identificarla: ```json { "message": "Validation failed for 1 event(s). No events were ingested.", "summary": { "total": 2, "with_errors": 1 }, "errors": [ { "index": 1, "event": { "employee_number": 9999, "timestamp": "2026-06-05T18:03:00-06:00" }, "issues": [ { "field": "employee_number", "code": "EMPLOYEE_NOT_FOUND", "message": "That 'employee_number' does not exist in this organization." } ] } ] } ``` # Listar (/docs/developers/api-reference/time-clock-events/list) ## Descripcion general [#descripcion-general] Este endpoint devuelve un listado paginado de las **checadas** (marcas de entrada y salida) registradas en tu organización, sin importar por dónde entraron: la API pública, o la captura masiva estilo Excel dentro de la plataforma. Cada checada guarda dos tiempos: `timestamp`, el instante exacto de la marca en UTC, y `local_date`, el **día natural** al que pertenece según la zona horaria de asistencia de la organización. `local_date` es el día que agrupa la derivación de retardos, y es el campo sobre el que filtran `from` y `to`. Los resultados se ordenan del día más reciente al más antiguo. Requiere que la API key tenga el permiso **Listar checadas**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/time-clock-events` ## Consulta multiples checadas [#consulta-multiples-checadas] *** Devuelve todas las checadas de la organización, paginadas de 20 en 20. Fíjate en la segunda fila del ejemplo: la marca ocurrió a las `00:02` UTC del 5 de junio, pero su `local_date` es el **4 de junio** — en la zona horaria de la organización todavía era el día anterior. Siempre filtra por `local_date` (con `from` / `to`), nunca por la hora UTC. ## Query params [#query-params] | Parametro | Tipo | Default | Descripcion | | ----------------- | --------------------- | ------- | ----------------------------------------------------------- | | `page` | integer ≥ 1 | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `employee_number` | integer | - | Filtra las checadas de un solo empleado. | | `from` | string (`YYYY-MM-DD`) | - | Incluye checadas con `local_date` **mayor o igual** a esta. | | `to` | string (`YYYY-MM-DD`) | - | Incluye checadas con `local_date` **menor o igual** a esta. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/time-clock-events" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 12, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "timestamp": "2026-06-05T15:20:00.000Z", "local_date": "2026-06-05", "direction": "in", "source": "api", "device_id": "RELOJ-PLANTA-1", "employee_number": 1, "employee_name": "Jesús Muñoz Rivera", "legal_entity_number": 1, "legal_entity_name": "Comercializadora del Norte S.A. de C.V.", "created_at": "2026-06-05T15:20:04.000Z" }, { "timestamp": "2026-06-05T00:02:00.000Z", "local_date": "2026-06-04", "direction": "out", "source": "api", "device_id": "RELOJ-PLANTA-1", "employee_number": 1, "employee_name": "Jesús Muñoz Rivera", "legal_entity_number": 1, "legal_entity_name": "Comercializadora del Norte S.A. de C.V.", "created_at": "2026-06-05T00:02:03.000Z" } ] } ``` ## Filtra por empleado y periodo [#filtra-por-empleado-y-periodo] *** `employee_number`, `from` y `to` se combinan. El ejemplo trae las checadas del empleado `1` durante junio. La segunda fila llegó por la **captura masiva estilo Excel** dentro de la plataforma (`source: "excel"`), no por un dispositivo: por eso no trae `device_id`. Todas las fuentes conviven en el mismo listado. **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/time-clock-events?employee_number=1&from=2026-06-01&to=2026-06-30" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 2, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "timestamp": "2026-06-05T15:20:00.000Z", "local_date": "2026-06-05", "direction": "in", "source": "api", "device_id": "RELOJ-PLANTA-1", "employee_number": 1, "employee_name": "Jesús Muñoz Rivera", "legal_entity_number": 1, "legal_entity_name": "Comercializadora del Norte S.A. de C.V.", "created_at": "2026-06-05T15:20:04.000Z" }, { "timestamp": "2026-06-04T15:58:00.000Z", "local_date": "2026-06-04", "direction": "in", "source": "excel", "device_id": null, "employee_number": 1, "employee_name": "Jesús Muñoz Rivera", "legal_entity_number": 1, "legal_entity_name": "Comercializadora del Norte S.A. de C.V.", "created_at": "2026-06-06T18:00:00.000Z" } ] } ``` ## Campos de cada fila [#campos-de-cada-fila] | Campo | Tipo | Descripcion | | --------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `timestamp` | string (ISO 8601, UTC) | Instante exacto de la marca. | | `local_date` | string (`YYYY-MM-DD`) | Día natural de la marca en la zona horaria de asistencia de la organización. Es el día que agrupa la derivación de retardos. | | `direction` | `"in"` \| `"out"` \| null | Dirección declarada por el dispositivo. `null` cuando no la envía. | | `source` | `"api"` \| `"excel"` \| `"qr"` | Por dónde entró: la API pública, la captura masiva estilo Excel, o el escaneo de un código QR de checada desde la app. | | `device_id` | string \| null | Identificador del dispositivo que envió la marca. `null` en las marcas que no vienen de un dispositivo, como las de `source: "excel"` y `source: "qr"`. | | `employee_number` | integer | Número del empleado dentro de la organización. | | `employee_name` | string | Nombre completo del empleado. | | `legal_entity_number` | integer | Razón social del empleo. | | `legal_entity_name` | string | Nombre de la razón social. | | `created_at` | string (ISO 8601) | Cuándo se registró la marca en la plataforma. | ## Errores [#errores] | Status | Causa | | ------ | ----------------------------------------------------------------------------------------------------------------------------- | | `400` | Valor inválido en algún filtro (`employee_number` no entero, `from` / `to` con formato incorrecto, o `to` anterior a `from`). | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Listar checadas**. | # Crear (/docs/developers/api-reference/work-schedule-bundles/create) ## Descripcion general [#descripcion-general] Crea una rotación con las mismas reglas que la plataforma. La plataforma asigna el `work_schedule_bundle_number` y numera los pasos en el orden en que los envías. Se crea una rotación por solicitud. Requiere que la API key tenga el permiso **Crear rotaciones de horarios**. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/work-schedule-bundles` ## Campos del cuerpo [#campos-del-cuerpo] *** | Campo | Tipo | Requerido | Notas | | ------------------------------ | --------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Sí | 3 a 80 caracteres. No puede repetirse. | | `description` | string | No | Máximo 200 caracteres. | | `cycle_start_date` | string (`YYYY-MM-DD`) | Sí | Debe ser lunes. | | `sort_order` | integer | No | Default `0`. | | `steps` | object\[] | Sí | Al menos 2 pasos. | | `steps[].work_schedule_number` | integer | Sí | Un horario activo. Dos pasos seguidos no pueden repetir horario, contando del último al primero: aumenta las semanas en su lugar. | | `steps[].weeks` | integer | Sí | 1 a 52 semanas. | **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/work-schedule-bundles" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Planta rotativo", "cycle_start_date": "2026-09-07", "steps": [ { "work_schedule_number": 1, "weeks": 2 }, { "work_schedule_number": 3, "weeks": 1 } ] }' ``` **Respuesta** ```text 201 Created con la rotación, igual que una fila del listado. ``` ## Errores [#errores] | Status | Código | Causa | | ------ | ----------------------- | ---------------------------------------------------------------- | | `400` | `INVALID_INPUT` | Algún campo no cumple las reglas. | | `400` | `NOT_FOUND` | Algún `work_schedule_number` no existe en la organización. | | `400` | `INVALID_WORK_SCHEDULE` | Algún horario de los pasos está inactivo. | | `401` | | API key ausente o inválida. | | `403` | | La API key no tiene el permiso **Crear rotaciones de horarios**. | | `409` | `DUPLICATE_NAME` | Ya existe una rotación con ese nombre. | # Eliminar (/docs/developers/api-reference/work-schedule-bundles/delete) ## Descripcion general [#descripcion-general] Elimina una rotación. Solo se puede eliminar si ningún empleo activo o pendiente la tiene asignada. La respuesta exitosa es `204 No Content`. Requiere que la API key tenga el permiso **Eliminar rotaciones de horarios**. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/work-schedule-bundles/{work_schedule_bundle_number}` ## Solicitud [#solicitud] *** Sin cuerpo. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/work-schedule-bundles/1" \ -H "Authorization: Bearer $API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Errores [#errores] | Status | Código | Causa | | ------ | ----------------- | ------------------------------------------------------------------- | | `400` | | Número inválido en la URL. | | `401` | | API key ausente o inválida. | | `403` | | La API key no tiene el permiso **Eliminar rotaciones de horarios**. | | `404` | | La rotación no existe o ya fue eliminada. | | `409` | `HAS_EMPLOYMENTS` | Un empleo activo o pendiente tiene esta rotación. | # Editar (/docs/developers/api-reference/work-schedule-bundles/edit) ## Descripcion general [#descripcion-general] Actualiza una rotación. Envía solo los campos que cambian. Si envías `steps`, se reemplaza la lista completa y se renumera en el orden enviado. Un horario que la rotación ya tenía sigue siendo válido aunque después se haya desactivado; los horarios nuevos deben estar activos. Desactivar la rotación (`is_active: false`) solo la quita de las opciones para nuevas asignaciones; los empleos que ya la tienen siguen rotando. Requiere que la API key tenga el permiso **Actualizar rotaciones de horarios**. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/work-schedule-bundles/{work_schedule_bundle_number}` ## Campos del cuerpo [#campos-del-cuerpo] *** | Campo | Tipo | Notas | | ------------------ | -------------- | -------------------------------------------------------------------------------------------------- | | `name` | string | 3 a 80 caracteres. | | `description` | string \| null | `null` la borra. | | `cycle_start_date` | string | Lunes, `YYYY-MM-DD`. | | `sort_order` | integer | | | `is_active` | boolean | Activa o desactiva la rotación. | | `steps` | object\[] | Con las mismas reglas que al [crear](/docs/developers/api-reference/work-schedule-bundles/create). | **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/work-schedule-bundles/1" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "cycle_start_date": "2026-09-14" }' ``` **Respuesta** ```text 200 OK con la rotación actualizada. ``` ## Errores [#errores] | Status | Código | Causa | | ------ | ----------------------- | --------------------------------------------------------------------- | | `400` | `INVALID_INPUT` | Número inválido en la URL, cuerpo vacío o algún campo fuera de regla. | | `400` | `NOT_FOUND` | Algún `work_schedule_number` no existe en la organización. | | `400` | `INVALID_WORK_SCHEDULE` | Un horario nuevo en los pasos está inactivo. | | `401` | | API key ausente o inválida. | | `403` | | La API key no tiene el permiso **Actualizar rotaciones de horarios**. | | `404` | | La rotación no existe o fue eliminada. | | `409` | `DUPLICATE_NAME` | Ya existe una rotación con ese nombre. | # Listar (/docs/developers/api-reference/work-schedule-bundles/list) ## Descripcion general [#descripcion-general] Una **rotación** encadena horarios que se turnan por semanas: por ejemplo, dos semanas de matutino y una de nocturno. El ciclo empieza a contar en `cycle_start_date`, siempre un lunes, y al terminar el último paso vuelve al primero. Cada rotación se identifica por su `work_schedule_bundle_number`. Requiere que la API key tenga el permiso **Listar rotaciones de horarios**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/work-schedule-bundles` ## Consulta las rotaciones [#consulta-las-rotaciones] *** Devuelve las rotaciones paginadas de 20 en 20, ordenadas por número, con sus pasos en orden. ## Query params [#query-params] | Parametro | Tipo | Default | Descripcion | | ----------------------------- | ----------------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `work_schedule_bundle_number` | integer | - | Una rotación en particular. | | `name` | string | - | Nombre exacto. | | `active` | `true` \| `false` | - | Solo activas o solo inactivas. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/work-schedule-bundles" \ -H "Authorization: Bearer $API_KEY" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "work_schedule_bundle_number": 1, "name": "Planta rotativo", "description": null, "cycle_start_date": "2026-09-07", "is_active": true, "sort_order": 0, "steps": [ { "step_order": 1, "work_schedule_number": 1, "work_schedule_name": "Matutino", "weeks": 2 }, { "step_order": 2, "work_schedule_number": 3, "work_schedule_name": "Nocturno", "weeks": 1 } ], "created_at": "2026-09-01T18:00:00.000Z", "updated_at": "2026-09-01T18:00:00.000Z" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | ----------------------------------------------------------------- | | `400` | Filtro inválido. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Listar rotaciones de horarios**. | # Consultar (/docs/developers/api-reference/settlement-runs/detail) ## Descripcion general [#descripcion-general] Devuelve una nómina de finiquito o liquidación con los mismos campos que el [listado](/docs/developers/api-reference/settlement-runs/list), más `result`: el desglose de percepciones, deducciones y costo patronal que se ve en el recibo. `result` es `null` mientras la nómina no se ha calculado. Requiere que la API key tenga el permiso **Listar finiquitos y liquidaciones**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/settlement-runs/{payroll_run_number}` ## Path params [#path-params] *** | Parametro | Descripcion | | -------------------- | ---------------------------------------------- | | `payroll_run_number` | Número de la nómina dentro de la organización. | ## Campos de `result` [#campos-de-result] | Campo | Descripcion | | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `isr`, `imss_worker` | ISR retenido y cuota obrera del IMSS. | | `withheld_food_vouchers` | Vales de despensa dispersados a tarjeta que el finiquito descuenta en **Vales de despensa entregados**. | | `other_deductions` | Deducciones que no son ISR ni IMSS (créditos, pensión, etc.). Incluye `withheld_food_vouchers`. | | `daily_salary` | Salario diario usado en el cálculo. | | `total_employer_cost` | Costo patronal total. | | `perception_lines`, `deduction_lines`, `employer_lines` | Una línea por concepto, con `code`, `name` y `amount`. | Todos los montos llegan como texto decimal para conservar la precisión. **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/settlement-runs/31" \ -H "Authorization: Bearer $API_KEY" ``` **Respuesta (extracto)** ```json { "payroll_run_number": 31, "status": "closed", "termination_reason": "voluntary_resignation", "employee_number": 142, "net_pay": "8500.5", "result": { "isr": "1200", "imss_worker": "250", "withheld_food_vouchers": "0", "other_deductions": "49.5", "daily_salary": "500", "total_employer_cost": "900", "perception_lines": [ { "code": "P001", "name": "Sueldo", "amount": "7000" }, { "code": "P023", "name": "Prima vacacional", "amount": "3000" } ], "deduction_lines": [ { "code": "D001", "name": "ISR", "amount": "1200" } ], "employer_lines": [] } } ``` ## Errores [#errores] | Status | Causa | | ------ | --------------------------------------------------------------------- | | `400` | `payroll_run_number` inválido. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Listar finiquitos y liquidaciones**. | | `404` | La nómina no existe o no es un finiquito ni una liquidación. | # Listar (/docs/developers/api-reference/settlement-runs/list) ## Descripcion general [#descripcion-general] Este endpoint devuelve un listado paginado de las **nóminas de finiquito y liquidación**: las nóminas fuera de calendario que pagan a un empleado su salida. Cada una paga a un solo empleado y se identifica por su `payroll_run_number`. La API solo consulta estas nóminas. Se crean, calculan y cierran desde la plataforma. Requiere que la API key tenga el permiso **Listar finiquitos y liquidaciones**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/settlement-runs` ## Consulta las nóminas de salida [#consulta-las-nóminas-de-salida] *** Devuelve las nóminas paginadas de 20 en 20, de la más reciente a la más antigua. Los filtros se combinan. ## Query params [#query-params] | Parametro | Tipo | Default | Descripcion | | -------------------- | ----------- | ------- | ------------------------------------------------------------- | | `page` | integer ≥ 1 | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `employee_number` | integer | - | Solo las nóminas de un empleado. | | `status` | string | - | `draft`, `calculating`, `calculated`, `closed` o `cancelled`. | | `termination_reason` | string | - | Motivo de la baja. Ver la tabla de abajo. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/settlement-runs?status=closed" \ -H "Authorization: Bearer $API_KEY" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "payroll_run_number": 31, "status": "closed", "termination_reason": "voluntary_resignation", "termination_date": "2026-09-15T00:00:00.000Z", "final_period_start": "2026-09-01T00:00:00.000Z", "employee_number": 142, "employee_name": "Ana López Ruiz", "legal_entity_number": 1, "legal_entity_name": "Comercializadora del Norte S.A. de C.V.", "description": null, "total_perceptions": "10000", "total_deductions": "1499.5", "net_pay": "8500.5", "calculated_at": "2026-09-16T10:00:00.000Z", "closed_at": "2026-09-17T12:00:00.000Z", "created_at": "2026-09-16T09:00:00.000Z", "updated_at": "2026-09-17T12:00:00.000Z" } ] } ``` ## Campos de cada fila [#campos-de-cada-fila] | Campo | Tipo | Descripcion | | -------------------------------------------------- | --------------- | ------------------------------------------------------------------------ | | `payroll_run_number` | integer | Número de la nómina dentro de la organización. | | `status` | string | Estado de la nómina. | | `termination_reason` | string | Motivo de la baja. Decide qué indemnizaciones se pagan. | | `termination_date` | string | Último día trabajado. | | `final_period_start` | string | Primer día que ninguna nómina ordinaria había pagado. | | `employee_number`, `employee_name` | integer, string | Empleado al que paga la nómina. | | `legal_entity_number`, `legal_entity_name` | integer, string | Razón social del empleo. | | `total_perceptions`, `total_deductions`, `net_pay` | string \| null | Montos como texto decimal. `null` mientras la nómina no se ha calculado. | | `calculated_at`, `closed_at` | string \| null | Cuándo se calculó y cuándo se cerró. | ## Motivos de baja [#motivos-de-baja] | Valor | Significado | | ----------------------- | ---------------------- | | `unjustified_dismissal` | Despido injustificado. | | `justified_dismissal` | Despido justificado. | | `voluntary_resignation` | Renuncia voluntaria. | | `mutual_agreement` | Mutuo acuerdo. | | `contract_end` | Fin de contrato. | | `death` | Fallecimiento. | ## Errores [#errores] | Status | Causa | | ------ | --------------------------------------------------------------------- | | `400` | Filtro inválido. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Listar finiquitos y liquidaciones**. | # Crear (/docs/developers/api-reference/work-schedules/create) ## Descripcion general [#descripcion-general] Crea un horario con las mismas reglas que la plataforma. La plataforma asigna el `work_schedule_number`. Se crea un horario por solicitud. Requiere que la API key tenga el permiso **Crear horarios**. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/work-schedules` ## Campos del cuerpo [#campos-del-cuerpo] *** | Campo | Tipo | Requerido | Notas | | ------------- | --------- | --------- | -------------------------------------------------- | | `name` | string | Sí | 3 a 80 caracteres. No puede repetirse. | | `description` | string | No | Máximo 200 caracteres. | | `sort_order` | integer | No | Orden en la plataforma. Default `0`. | | `days` | object\[] | Sí | Exactamente 7, un `weekday` de `0` a `6` cada uno. | Cada día lleva los campos de la tabla del [listado](/docs/developers/api-reference/work-schedules/list). En un día laborable, `start_time` y `end_time` son obligatorios y distintos. `tolerance_minutes` es `10` si no se envía, con máximo `720`. La comida, si va, debe caer dentro de la jornada. **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/work-schedules" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Matutino", "days": [ { "weekday": 0, "is_workday": false }, { "weekday": 1, "is_workday": true, "start_time": "09:00", "end_time": "18:00", "meal_start_time": "14:00", "meal_end_time": "15:00" }, { "weekday": 2, "is_workday": true, "start_time": "09:00", "end_time": "18:00" }, { "weekday": 3, "is_workday": true, "start_time": "09:00", "end_time": "18:00" }, { "weekday": 4, "is_workday": true, "start_time": "09:00", "end_time": "18:00" }, { "weekday": 5, "is_workday": true, "start_time": "09:00", "end_time": "18:00" }, { "weekday": 6, "is_workday": false } ] }' ``` **Respuesta** ```text 201 Created con el horario, igual que una fila del listado. ``` ## Errores [#errores] | Status | Código | Causa | | ------ | ---------------- | -------------------------------------------------------------------------- | | `400` | `INVALID_INPUT` | Algún campo no cumple las reglas. El día se indica como `days[weekday=3]`. | | `401` | | API key ausente o inválida. | | `403` | | La API key no tiene el permiso **Crear horarios**. | | `409` | `DUPLICATE_NAME` | Ya existe un horario con ese nombre. | # Eliminar (/docs/developers/api-reference/work-schedules/delete) ## Descripcion general [#descripcion-general] Elimina un horario. Solo se puede eliminar si nada lo usa: ningún empleo activo o pendiente, no es el horario default de la organización y no es un paso de una rotación. La respuesta exitosa es `204 No Content`. Requiere que la API key tenga el permiso **Eliminar horarios**. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/work-schedules/{work_schedule_number}` ## Solicitud [#solicitud] *** Sin cuerpo. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/work-schedules/1" \ -H "Authorization: Bearer $API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Errores [#errores] | Status | Código | Causa | | ------ | --------------------- | ----------------------------------------------------- | | `400` | | Número inválido en la URL. | | `401` | | API key ausente o inválida. | | `403` | | La API key no tiene el permiso **Eliminar horarios**. | | `404` | | El horario no existe o ya fue eliminado. | | `409` | `HAS_EMPLOYMENTS` | Un empleo activo o pendiente tiene este horario. | | `409` | `IS_DEFAULT_SCHEDULE` | Es el horario default de la organización. | | `409` | `HAS_BUNDLES` | Una rotación lo usa como paso. | # Editar (/docs/developers/api-reference/work-schedules/edit) ## Descripcion general [#descripcion-general] Actualiza un horario. Envía solo los campos que cambian. Si envías `days`, se reemplazan los 7 días completos. Editar un horario solo afecta los retardos que se deriven de aquí en adelante: los días ya derivados no se reescriben. Desactivarlo (`is_active: false`) solo lo quita de las opciones para nuevas asignaciones; los empleos que ya lo tienen lo conservan. Requiere que la API key tenga el permiso **Actualizar horarios**. ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/work-schedules/{work_schedule_number}` ## Campos del cuerpo [#campos-del-cuerpo] *** | Campo | Tipo | Notas | | ------------- | -------------- | ------------------------------------------------------------------------------------------------------- | | `name` | string | 3 a 80 caracteres. | | `description` | string \| null | `null` la borra. | | `sort_order` | integer | | | `is_active` | boolean | Activa o desactiva el horario. | | `days` | object\[] | Los 7 días, con las mismas reglas que al [crear](/docs/developers/api-reference/work-schedules/create). | **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/work-schedules/1" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Matutino planta", "is_active": false }' ``` **Respuesta** ```text 200 OK con el horario actualizado. ``` ## Errores [#errores] | Status | Código | Causa | | ------ | ---------------- | --------------------------------------------------------------------- | | `400` | `INVALID_INPUT` | Número inválido en la URL, cuerpo vacío o algún campo fuera de regla. | | `401` | | API key ausente o inválida. | | `403` | | La API key no tiene el permiso **Actualizar horarios**. | | `404` | | El horario no existe o fue eliminado. | | `409` | `DUPLICATE_NAME` | Ya existe un horario con ese nombre. | # Listar (/docs/developers/api-reference/work-schedules/list) ## Descripcion general [#descripcion-general] Este endpoint devuelve un listado paginado de los **horarios**: la jornada de cada día de la semana contra la que se derivan los retardos. Cada horario se identifica por su `work_schedule_number`. Requiere que la API key tenga el permiso **Listar horarios**. ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/work-schedules` ## Consulta los horarios [#consulta-los-horarios] *** Devuelve los horarios paginados de 20 en 20, ordenados por número. Cada uno trae sus 7 días. ## Query params [#query-params] | Parametro | Tipo | Default | Descripcion | | ---------------------- | ----------------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `work_schedule_number` | integer | - | Un horario en particular. | | `name` | string | - | Nombre exacto. | | `active` | `true` \| `false` | - | Solo activos o solo inactivos. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/work-schedules?active=true" \ -H "Authorization: Bearer $API_KEY" ``` **Respuesta (extracto)** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "work_schedule_number": 1, "name": "Matutino", "description": null, "is_active": true, "sort_order": 0, "days": [ { "weekday": 0, "is_workday": false, "start_time": null, "end_time": null, "meal_start_time": null, "meal_end_time": null, "tolerance_minutes": 10, "meal_tolerance_minutes": null }, { "weekday": 1, "is_workday": true, "start_time": "09:00", "end_time": "18:00", "meal_start_time": "14:00", "meal_end_time": "15:00", "tolerance_minutes": 10, "meal_tolerance_minutes": 5 } ], "created_at": "2026-07-24T18:00:00.000Z", "updated_at": "2026-07-24T18:00:00.000Z" } ] } ``` ## Campos de cada día [#campos-de-cada-día] | Campo | Tipo | Descripcion | | ---------------------------------- | --------------- | ----------------------------------------------------------------------------------------------- | | `weekday` | integer | `0` domingo a `6` sábado. | | `is_workday` | boolean | Si se trabaja ese día. Un día de descanso no lleva horas. | | `start_time`, `end_time` | string \| null | Entrada y salida en `HH:mm`. Una salida menor a la entrada es un turno que cruza la medianoche. | | `meal_start_time`, `meal_end_time` | string \| null | Salida y regreso de comer. Van juntos o no van. | | `tolerance_minutes` | integer | Minutos de tolerancia a la entrada antes de contar un retardo. | | `meal_tolerance_minutes` | integer \| null | Tolerancia al regreso de comer. `null` hereda `tolerance_minutes`. | ## Errores [#errores] | Status | Causa | | ------ | --------------------------------------------------- | | `400` | Filtro inválido. | | `401` | API key ausente o inválida. | | `403` | La API key no tiene el permiso **Listar horarios**. | # Crear (/docs/developers/api-reference/custom-fields/employees/create) ## Descripcion general [#descripcion-general] Este endpoint permite crear una o varias definiciones de campos personalizados de empleados. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. Cada fila debe enviar `name`, `key` y `type`. Una solicitud exitosa devuelve `201` con las definiciones creadas, incluyendo los timestamps generados por el servidor (`created_at`, `updated_at`). En modo lote, si **cualquier** fila falla la validación, **ninguna** definición se crea — la respuesta lista los problemas por fila para corregirlos en una sola pasada. `key` es el identificador público de la definición y se usa como segmento de URL en `PATCH` y `DELETE`, además de ser referenciado por integraciones de nómina. Usa solo minúsculas, números y guiones bajos. Debe iniciar con letra. Máx. 60 caracteres y único por organización. **Claves reservadas.** Como los valores de campos personalizados se envían como claves planas en la raíz del cuerpo del empleado (junto a `first_name`, `rfc`, `department_code`, etc.), el `key` que elijas no puede colisionar con un campo nativo del empleado. Si lo hace, la solicitud falla con `400`. `type` determina en qué columna tipada de `employee_custom_field_value` se almacenan los valores. Cuando `type === "select"`, debes enviar `select_options` con al menos una opción no vacía; para cualquier otro tipo, `select_options` se ignora. ## Endpoint [#endpoint] `POST https://app.integranom.com/api/v1/employee-custom-fields` ## Solicitud unica — campo de texto [#solicitud-unica--campo-de-texto] *** Envía los tres campos obligatorios. Los flags y `sort_order` toman sus valores por defecto (`false` / `0`), y `description` queda en `null`. ```json { "name": "Talla de playera", "key": "shirt_size", "type": "text" } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employee-custom-fields" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Talla de playera", "key": "shirt_size", "type": "text" }' ``` **Respuesta** ```json { "key": "shirt_size", "name": "Talla de playera", "type": "text", "description": null, "select_options": null, "is_required_for_create": false, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ## Solicitud unica — tipo select con opciones [#solicitud-unica--tipo-select-con-opciones] *** Cuando `type === "select"`, `select_options` es obligatorio y debe traer al menos una opción no vacía. Las opciones se almacenan internamente como un arreglo JSON. ```json { "name": "Talla de playera", "key": "shirt_size", "type": "select", "select_options": ["XS", "S", "M", "L", "XL"], "is_required_for_create": true } ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employee-custom-fields" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Talla de playera", "key": "shirt_size", "type": "select", "select_options": ["XS", "S", "M", "L", "XL"], "is_required_for_create": true }' ``` **Respuesta** ```json { "key": "shirt_size", "name": "Talla de playera", "type": "select", "description": null, "select_options": ["XS", "S", "M", "L", "XL"], "is_required_for_create": true, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ## Solicitud por lote (1–100 definiciones) [#solicitud-por-lote-1100-definiciones] *** Envía un **arreglo** de hasta 100 definiciones en el mismo endpoint. Si **cualquier** fila falla la validación, **ninguna** definición se crea y la respuesta lista los problemas por fila. ```json [ { "name": "Talla de playera", "key": "shirt_size", "type": "text" }, { "name": "Alérgenos", "key": "allergens", "type": "text", "description": "Listado libre." }, { "name": "Apto para guardia", "key": "fit_for_duty", "type": "boolean" } ] ``` **cURL** ```bash curl -X POST "https://app.integranom.com/api/v1/employee-custom-fields" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "name": "Talla de playera", "key": "shirt_size", "type": "text" }, { "name": "Alérgenos", "key": "allergens", "type": "text", "description": "Listado libre." }, { "name": "Apto para guardia", "key": "fit_for_duty", "type": "boolean" } ]' ``` **Respuesta** ```json { "summary": { "total": 3, "created": 3 }, "created": [ { "key": "shirt_size", "name": "Talla de playera", "type": "text", "description": null, "select_options": null, "is_required_for_create": false, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" }, { "key": "allergens", "name": "Alérgenos", "type": "text", "description": "Listado libre.", "select_options": null, "is_required_for_create": false, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" }, { "key": "fit_for_duty", "name": "Apto para guardia", "type": "boolean", "description": null, "select_options": null, "is_required_for_create": false, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Reglas de los campos [#reglas-de-los-campos] | Campo | Tipo | Requerido | Notas | | ------------------------- | ----------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Sí | No vacío tras recortar. Etiqueta legible. | | `key` | string | Sí | Identificador público. Usa solo minúsculas, números y guiones bajos. Debe iniciar con letra. Máx. 60 caracteres. Único por organización. Usado como segmento de URL en `PATCH`/`DELETE` y referenciado por integraciones de nómina. | | `type` | `text` \| `number` \| `date` \| `boolean` \| `select` | Sí | Tipo de almacenamiento. Determina en qué columna tipada de `employee_custom_field_value` viven los valores. | | `description` | string | No | Pista interna mostrada en interfaces administrativas. | | `select_options` | string\[] | Condicional | Obligatorio cuando `type === "select"` (mínimo 1 opción no vacía). Ignorado para cualquier otro tipo. | | `is_required_for_create` | boolean | No | Por defecto `false`. Cuando es `true`, `POST /api/v1/employees` rechaza filas que no envíen valor para este campo. | | `is_required_for_payroll` | boolean | No | Por defecto `false`. Bandera informativa consumida por las exportaciones de nómina. | | `is_active` | boolean | No | Por defecto `true`. | | `sort_order` | integer ≥ 0 | No | Por defecto `0`. Usado para ordenar definiciones en selectores. | ## Forma de las respuestas [#forma-de-las-respuestas] ### `201` — solicitud única [#201--solicitud-única] Cuando el cuerpo es **un objeto**, la respuesta es el objeto de la definición creada. ```json { "key": "shirt_size", "name": "Talla de playera", "type": "select", "description": null, "select_options": ["XS", "S", "M", "L", "XL"], "is_required_for_create": true, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ``` ### `201` — solicitud por lote [#201--solicitud-por-lote] Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`. ```json { "summary": { "total": 3, "created": 3 }, "created": [ { "key": "shirt_size", "name": "Talla de playera", "...": "..." }, { "key": "allergens", "name": "Alérgenos", "...": "..." }, { "key": "fit_for_duty", "name": "Apto para guardia", "...": "..." } ] } ``` ## Errores [#errores] ### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única] Para fallas de validación de esquema (campo faltante, tipo incorrecto), `errors` es un objeto indexado por campo: ```json { "message": "Validation failed.", "errors": { "select_options": ["Agrega al menos una opción para campos tipo selección."] } } ``` #### Clave reservada por un campo nativo [#clave-reservada-por-un-campo-nativo] Si el `key` que envías colisiona con un campo nativo del empleado (`first_name`, `rfc`, `notes`, `department_code`, etc.), la validación responde con: ```json { "message": "Validation failed.", "errors": { "key": ["Esta clave está reservada por un campo nativo del empleado. Usa otra clave."] } } ``` ### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote] Reporte por fila. **Ninguna** definición del lote fue creada. ```json { "message": "Validation failed for 2 employee custom field(s). No fields were created.", "summary": { "total": 3, "with_errors": 2 }, "errors": [ { "index": 0, "employee_custom_field": { "name": "Talla de playera", "key": "shirt_size" }, "issues": [ { "field": "key", "code": "DUPLICATE_KEY", "message": "An employee custom field with key 'shirt_size' already exists in this organization." } ] }, { "index": 2, "employee_custom_field": { "name": "Talla", "key": "shirt_size" }, "issues": [ { "field": "key", "code": "DUPLICATE_KEY_IN_BATCH", "message": "Key 'shirt_size' appears multiple times in this batch." } ] } ] } ``` * `index` — posición 0-based de la fila que falló dentro del arreglo enviado. * `employee_custom_field.name` / `employee_custom_field.key` — extraídos de tu entrada para que identifiques la fila sin contar. * `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar. ### `409` — condición de carrera [#409--condición-de-carrera] Otra solicitud reclamó el mismo `key` entre el momento en que validamos el tuyo y el momento en que escribimos. En modo lote, la transacción completa hace rollback — ninguna definición es creada. Reintenta la misma solicitud. ## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error] Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos: | Codigo | Significado | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `INVALID_INPUT` | Falla genérica de validación de esquema (`type` fuera del enum, `key` mal formada, `select_options` ausente con `type === "select"`, etc.). | | `DUPLICATE_KEY` | El `key` ya existe en otra definición de la organización. | | `DUPLICATE_KEY_IN_BATCH` | El mismo `key` aparece en más de una fila del lote. | | `CONCURRENT_MODIFICATION` | Señal de reintento por condición de carrera (respuesta `409`). | # Eliminar (/docs/developers/api-reference/custom-fields/employees/delete) ## Descripcion general [#descripcion-general] Este endpoint realiza el **borrado** de la definición: a partir de ese momento deja de aparecer en `GET /api/v1/employee-custom-fields` y se trata como no encontrada en `PATCH`/`DELETE` (`404`). La respuesta exitosa es `204 No Content` con cuerpo vacío. **Los valores históricos se conservan.** Los valores ya capturados para esta definición **no se borran**, para mantener consistentes las exportaciones de nómina y reportes previos. Sin verificación de "valores asignados": el borrado se permite siempre. ## Endpoint [#endpoint] `DELETE https://app.integranom.com/api/v1/employee-custom-fields/{key}` **Path param:** `key` — el `key` actual de la definición. Usa solo minúsculas, números y guiones bajos. Debe iniciar con letra. ## Solicitud [#solicitud] *** Sin cuerpo. La API responde `204 No Content` si el borrado tuvo éxito. **cURL** ```bash curl -X DELETE "https://app.integranom.com/api/v1/employee-custom-fields/shirt_size" \ -H "Authorization: Bearer TU_API_KEY" ``` **Respuesta** ```text 204 No Content ``` ## Comportamiento [#comportamiento] * Marca la definición como eliminada. * La fila deja de devolverse por `GET /api/v1/employee-custom-fields`. * Los valores históricos capturados se conservan. * Sin verificación de valores asignados: el borrado siempre se permite. ## Errores [#errores] | Status | Causa | | ------ | ---------------------------------------------------------------- | | `400` | Formato inválido del `key` en la URL. | | `404` | La definición no existe o ya fue eliminada en esta organización. | # Editar (/docs/developers/api-reference/custom-fields/employees/edit) ## Descripcion general [#descripcion-general] Este endpoint permite actualizar parcialmente una definición de campo personalizado. Todos los campos del cuerpo son **opcionales**: si omites una clave, ese campo no se toca; si la envías como `null`, ese campo se limpia (cuando aplica). El identificador en la URL es el **`key` actual** de la definición. Una solicitud exitosa devuelve `200` con la forma completa de la definición actualizada. Las definiciones eliminadas se tratan como no encontradas (`404`). **`key` es editable, pero con cuidado.** El `key` es el identificador público referenciado por integraciones externas. Si lo cambias, el segmento de URL para `PATCH`/`DELETE` cambia con él — las llamadas siguientes deben usar el nuevo `key`. Las integraciones que hardcodeen el `key` original dejarán de funcionar. Además, el nuevo `key` no puede colisionar con un campo nativo del empleado (`first_name`, `rfc`, `notes`, `department_code`, etc.); si lo hace, la solicitud falla con `400`. **`type` es editable, pero generalmente no debería cambiar.** Los valores existentes en `employee_custom_field_value` viven en la columna tipada que corresponde al `type` **original**. Cambiar `type` **no migra** los valores: se quedan en su columna anterior y pueden no devolverse cuando los lectores esperan el tipo nuevo. Solo cámbialo si no hay valores aún (o si los limpiaste manualmente). ## Endpoint [#endpoint] `PATCH https://app.integranom.com/api/v1/employee-custom-fields/{key}` **Path param:** `key` — el `key` actual de la definición. Usa solo minúsculas, números y guiones bajos. Debe iniciar con letra. Máx. 60. ## Renombrar el campo [#renombrar-el-campo] *** Envía solo los campos que quieras cambiar. El resto de la definición queda intacto. ```json { "name": "Talla de uniforme" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employee-custom-fields/shirt_size" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Talla de uniforme" }' ``` **Respuesta** ```json { "key": "shirt_size", "name": "Talla de uniforme", "type": "select", "description": "Talla solicitada por el empleado.", "select_options": ["XS", "S", "M", "L", "XL"], "is_required_for_create": false, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:15:32.000Z" } ``` ## Agregar opciones a un select [#agregar-opciones-a-un-select] *** Mientras `type` permanece como `select`, puedes reescribir el arreglo completo de `select_options`. El handler valida contra el **estado efectivo** (lo que enviaste + lo que ya estaba almacenado). ```json { "select_options": ["XS", "S", "M", "L", "XL", "XXL"] } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employee-custom-fields/shirt_size" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "select_options": ["XS", "S", "M", "L", "XL", "XXL"] }' ``` **Respuesta** ```json { "key": "shirt_size", "name": "Talla de uniforme", "type": "select", "description": "Talla solicitada por el empleado.", "select_options": ["XS", "S", "M", "L", "XL", "XXL"], "is_required_for_create": false, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:18:09.000Z" } ``` ## Cambiar un text a select [#cambiar-un-text-a-select] *** Para convertir un campo de `text` (u otro tipo) a `select`, envía el nuevo `type` junto con `select_options`. Si la nueva `type` es `select` y no hay opciones (ni en el cuerpo ni almacenadas), el request se rechaza con `400`. ```json { "type": "select", "select_options": ["A", "B"] } ``` Si en cambio quitas el tipo `select` (por ejemplo `{ "type": "text" }`), las `select_options` existentes se **auto-limpian** (se ponen en `null`) sin importar lo que envíes. **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employee-custom-fields/notes" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "select", "select_options": ["A", "B"] }' ``` **Respuesta** ```json { "key": "notes", "name": "Notas", "type": "select", "description": null, "select_options": ["A", "B"], "is_required_for_create": false, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:22:44.000Z" } ``` ## Renombrar el key [#renombrar-el-key] *** Si renombras `key`, la URL para llamadas siguientes cambia: a partir de aquí debes usar `/api/v1/employee-custom-fields/uniform_size` (no `/shirt_size`). Cualquier integración externa que hardcodee el `key` original debe actualizarse. ```json { "key": "uniform_size" } ``` **cURL** ```bash curl -X PATCH "https://app.integranom.com/api/v1/employee-custom-fields/shirt_size" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "uniform_size" }' ``` **Respuesta** ```json { "key": "uniform_size", "name": "Talla de uniforme", "type": "select", "description": "Talla solicitada por el empleado.", "select_options": ["XS", "S", "M", "L", "XL", "XXL"], "is_required_for_create": false, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T11:30:00.000Z" } ``` ## Campos del cuerpo [#campos-del-cuerpo] | Campo | Tipo | Notas | | ------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | No vacío tras recortar. | | `key` | string | Editable, pero las integraciones externas pueden hardcodear el valor original. Si lo cambias, el segmento de URL cambia — las llamadas siguientes deben usar el nuevo `key`. | | `type` | `text` \| `number` \| `date` \| `boolean` \| `select` | Editable, pero los valores existentes en `employee_custom_field_value` viven en la columna tipada del `type` original. Cambiar `type` **no migra** los valores. | | `description` | string \| null | `null` o `""` limpian la descripción. | | `select_options` | string\[] | Requerido cuando el `type` efectivo es `select` (el handler verifica el estado fusionado: lo enviado + lo actual). Si cambias `type` a algo distinto de `select`, las opciones se auto-limpian. | | `is_required_for_create` | boolean | | | `is_required_for_payroll` | boolean | | | `is_active` | boolean | Flag activo controlado por el usuario. Independiente del borrado. | | `sort_order` | integer ≥ 0 | | ### Interacción `type` ↔ `select_options` [#interacción-type--select_options] * Si el `type` efectivo nuevo es `select` y no hay opciones (ni en el cuerpo ni almacenadas), la solicitud se rechaza con `400`. * Si cambias `type` saliendo de `select`, las `select_options` existentes se **auto-limpian** (se ponen en `null`) sin importar lo que envíes. * Si `type` no cambia y el caller toca `select_options`, solo se reescriben las opciones. ## Forma de la respuesta [#forma-de-la-respuesta] `200 OK` — misma forma que la respuesta de [crear](/docs/developers/api-reference/custom-fields/employees/create) en modo single. ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | Formato inválido del `key` en la URL; falla de validación del cuerpo; el `type` efectivo nuevo es `select` sin `select_options`; el nuevo `key` colisiona con un campo nativo del empleado. | | `404` | La definición no existe (o fue eliminada) en esta organización. | | `409` | El nuevo `key` ya está en uso por otra definición viva de la organización. | # Listar (/docs/developers/api-reference/custom-fields/employees/list) ## Descripcion general [#descripcion-general] Este endpoint permite obtener un listado paginado de las **definiciones de campos personalizados de empleados** en tu organización. Los resultados se devuelven en formato JSON e incluyen metadatos de paginación. Las definiciones eliminadas se excluyen automáticamente. El tamaño de página es fijo en 20 resultados. `key` es el identificador público de cada definición (snake\_case, único por organización) y se usa como segmento de URL en `PATCH` y `DELETE`. `select_options` se devuelve como arreglo de strings (vacío o ausente cuando `type` no es `select`). ## Endpoint [#endpoint] `GET https://app.integranom.com/api/v1/employee-custom-fields` ## Consulta multiples definiciones [#consulta-multiples-definiciones] *** ## Query params [#query-params] | Parametro | Tipo | Requerido | Default | Descripcion | | --------- | --------------------- | --------- | ------- | ----------------------------------------------------- | | `page` | integer ≥ 1 | No | 1 | Página solicitada. El tamaño de página es fijo en 20. | | `active` | `"true"` \| `"false"` | No | - | Filtra por el flag `is_active`. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/employee-custom-fields" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 4, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "key": "shirt_size", "name": "Talla de playera", "type": "select", "description": "Talla solicitada por el empleado.", "select_options": ["XS", "S", "M", "L", "XL"], "is_required_for_create": false, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Consulta una definicion especifica [#consulta-una-definicion-especifica] *** Se puede buscar una definición particular por `key` (snake\_case, coincidencia exacta) o por `type` (uno de los cinco valores del enum). ## Query params [#query-params-1] | Parametro | Tipo | Requerido | Default | Descripcion | | --------- | ----------------------------------------------------- | --------- | ------- | ------------------------- | | `key` | string | No | - | Filtra por `key` exacto. | | `type` | `text` \| `number` \| `date` \| `boolean` \| `select` | No | - | Filtra por tipo de campo. | **cURL** ```bash curl -X GET "https://app.integranom.com/api/v1/employee-custom-fields?key=shirt_size" \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" ``` **Respuesta** ```json { "count": 1, "page": 1, "page_size": 20, "total_pages": 1, "next": null, "previous": null, "results": [ { "key": "shirt_size", "name": "Talla de playera", "type": "select", "description": "Talla solicitada por el empleado.", "select_options": ["XS", "S", "M", "L", "XL"], "is_required_for_create": false, "is_required_for_payroll": false, "is_active": true, "sort_order": 0, "created_at": "2026-05-25T10:00:00.000Z", "updated_at": "2026-05-25T10:00:00.000Z" } ] } ``` ## Errores [#errores] | Status | Causa | | ------ | ------------------------------------------------------------------------------------ | | `400` | Valor inválido en `type` (debe ser uno de los cinco valores del enum) o en `active`. | | `401` | API key ausente o inválida. |